Pergunta

Recentemente pediu um pergunta sobre programação funcional, e recebeu (bom!) respostas que levaram mais perguntas (como parece ser o caso com a aprendizagem, por vezes). Aqui estão alguns exemplos:

  1. Uma resposta feita referência a uma vantagem de estruturas de dados imutáveis: cada thread pode ter a sua própria cópia. Agora, para mim, isso soa um pouco como um sistema de controle de versão (para usar uma analogia), onde em vez de código de bloqueio que alguém tenha verificado de modo que não pode ser modificado por qualquer outra pessoa, todos podem verificar as suas próprias cópias. Parece bom. No entanto, em VCS você tem o conceito de "fusão" alterações, no caso em que duas pessoas mudou a mesma coisa. Parece que esta questão certamente poderia chegar em um cenário multithreaded ... então como é "fusão" feito quando é importante que tópicos ver os dados mais recentes?

  2. Esta resposta falou sobre o caso em que as operações foram sendo realizada em um loop em um objeto, e como você pode usar um novo objeto de cada vez através de em vez de atualizar um antigo. No entanto, digamos que o bankAccount está sendo atualizado em um cenário não-loop - por exemplo, um sistema de GUI bancário. O operador clica no botão "Alterar Taxa de Juros", que dispara um evento que (em C #, por exemplo) fazer algo como bankAccount.InterestRate = newRateFromUser. Eu sinto que estou sendo densa aqui, mas espero que o meu exemplo faz sentido: tem que haver alguma forma que o objeto é atualizado, certo? Várias outras coisas pode depender do os novos dados.

De qualquer forma, se você pode me ajudar a obter minha cabeça em torno da mudança de paradigma, eu ficaria grato. Lembro-me de meu cérebro passando por "fases estúpidas" semelhantes ao aprender OOP depois de um fundo da abordagem imperativo processual simples de codificação.

Foi útil?

Solução

resposta para a parte 1: Imutável objetos por si só não apoiar qualquer coisa como "fusão" para permitir que os resultados de atualizações dois de linha para ser combinados. Há duas estratégias principais para isso: o pessimista e do otimista. Se você é pessimista, você assumir que é bastante provável que dois threads vai querer atualizar o mesmo pedaço de dados ao mesmo tempo. Então você empregar bloqueio, de modo que o segundo segmento vai congelar até a primeira linha diz ter terminado. Se você está otimista de que isso vai acontecer apenas raramente, você deixa ambos os tópicos trabalhar em sua própria cópia lógica dos dados. A única que termina primeiros fornecimentos a nova versão, eo outro tem de começar de novo desde o início - só agora começa a partir dos resultados de alterações da primeira linha. Este reinício caro só acontece de vez em quando, porém, assim sobre tudo o que tem melhor desempenho devido à falta de bloqueio (embora isto só é verdade se o seu otimismo estava bem colocada sobre como raramente ocorre um choque).

Parte 2: idiomas apátridas funcionais puros não realmente eliminar esse problema. Mesmo um programa Haskell puro pode ter estado associado a ele. A diferença é que o código stateful tem um tipo de retorno diferente. Uma função que manipula estado é expressa como uma sequência de operações que operam sobre um objecto que representa esse estado. Em um exemplo absurdo, considere sistema de arquivos do computador. Cada vez que um programa modifica o conteúdo de um arquivo (mesmo por um único byte) criou uma nova "versão" de todo o sistema de arquivos. E, por extensão, uma nova versão de todo o universo. Mas vamos nos concentrar no sistema de arquivos para agora. Qualquer outra parte do programa que analisa o sistema de arquivos agora podem ser afetados por esse byte modificado. Então Haskell diz que as funções que operam no sistema de arquivos deve efetivamente passar em torno de um objeto que representa uma versão do sistema de arquivos. Então, porque isso seria tedioso para lidar manualmente com, ao que parece o interior exigência e diz que se uma função quer ser capaz de fazer IO, ele deve retornar um tipo de objeto de recipiente. Dentro do recipiente é o valor da função quer voltar. Mas o recipiente serve como prova de que a função seja também tem efeitos secundários ou pode ver efeitos colaterais. Isso significa que tipo de sistema de Haskell é capaz de distinguir entre as funções-com-efeitos colaterais e funções "puros". Por isso ajuda a conter e gerir a statefulness de código, sem realmente eliminá-lo.

Outras dicas

Pense sobre a classe String em .Net (que é um objeto imutável). Se você chamar um método em uma corda, você começa uma nova cópia:

String s1 = "there";
String s2 = s1.Insert(0, "hello ");

Console.Writeline("string 1: " + s1);
Console.Writeline("string 2: " + s2);

A saída será:

string 1: não

string 2: Olá lá

Compare esse comportamento para StringBuilder, que tem basicamente a mesma assinatura do método:

StringBuilder sb  = new StringBuilder("there");
StringBuilder sb2 = sb.Insert(0, "hi ");

Console.WriteLine("sb 1: " + sb.ToString());
Console.WriteLine("sb 2: " + sb2.ToString());

Porque StringBuilder é mutável, ambas as variáveis ??apontam para o mesmo objeto. A saída será:

sb 1: oi lá

sb 2: oi lá

Assim, você absolutamente não pode mudar uma corda uma vez que você criou. s1 irá sempre ser "não" até ao fim do tempo (ou até que o seu lixo recolhido). Isso é importante para enfiar porque você sempre pode passo através de cada personagem e imprimir o seu valor, sabendo que ele sempre vai imprimir 'lá'. Se você começou a imprimir o StringBuilder depois que ele foi criado, você pode imprimir os dois primeiros caracteres de lá e get'th'. Agora, imagine outro segmento vem junto inserções de anúncios 'oi'. O valor agora é diferente! Quando você imprime o terceiro personagem, é o espaço de 'oi'. Então você imprimir:. 'Th lá'

Com relação a # 2 ...

Várias outras coisas pode depender do os novos dados.

Isto é o que os puristas chamam de "efeito". A noção de múltiplas referências de objeto para o mesmo objeto mutável é a essência do estado mutável e o cerne da questão. Em OOP, você pode ter um objeto "a" do tipo BankAccount, e se você ler a.Balance ou outros enfeites em diferentes vezes você pode ver valores diferentes. Em contraste, em FP pura, se "a" tem o tipo BankAccount, então é imutável e tem o mesmo valor independentemente do tempo.

Uma vez que no entanto um BankAccount é presumivelmente um objeto queremos modelo cujo estado não variam com o tempo, faríamos em FP codificar a informação no tipo. Assim, "a" pode ter tipo "IO BankAccount", ou algum outro tipo monádico que basicamente se resume a fazer "a", na verdade, ser uma função que toma como entrada "o estado anterior do mundo" (ou estado anterior das taxas de juro bancárias , ou qualquer outro), e retorna um novo estado do mundo. Atualizando a taxa de juros seria outra operação com um tipo que representa o efeito (por exemplo, uma outra operação IO), e, portanto, retornaria um 'mundo' nova, e tudo o que pode depender da taxa de juros (estado mundial) seria de dados com uma tipo que sabe que precisa tomar esse mundo como entrada.

Como resultado, a única maneira possível chamar "a.Balance" ou outros enfeites é use o código que, graças aos tipos estáticos, Impõe que alguns 'história do mundo que nos trouxe até aqui' foi devidamente sondado para o ponto da chamada, e tudo o que a história do mundo é a entrada afeta o que resultará vamos começar a partir a.Balance.

Lendo-se na Estado Mônada pode ser útil para ter uma noção de como você modela 'compartilhada estado mutável' puramente.

  1. estruturas de dados imutáveis ??não são como VCS. Pense em uma estrutura de dados imutáveis ??como um arquivo somente leitura. Se sua somente leitura, não importa quem está lendo a parte do arquivo em um determinado momento, todo mundo vai ler as informações corretas.

  2. Essa resposta está falando sobre http://en.wikipedia.org/ wiki / Monad_ (functional_programming)

MVCC ( multiversão Concorrência Controle )

A solução para o problema que você está se referindo é descrito por Rich Hickey em suas apresentações de vídeo .

Em suma : em vez de passar os dados por referência diretamente aos clientes, você adicionar mais um nível de engano e passar a referência para a referência aos dados. ( Bem, na verdade você gostaria de ter pelo menos mais um nível de engano. Mas vamos supor que a estrutura de dados é muito simples, como o "array". )
Desde que os dados é imutável, cada vez que os dados devem ser alterados, de criar a cópia da parte alterada ( em caso da matriz você deve criar outra matriz! ) e você criar outra referência para todo o dados "mudou".
Assim, para todos os clientes que utilizaram a versão 1º da matriz, eles usam a referência à primeira versão. Cada cliente tentando acessar a 2ª versão usa a segunda referência.
A estrutura de dados "array" não é muito interessante para este método, porque você não pode dividir os dados e você é forçado a copiar tudo. Mas para estruturas de dados mais sofisticados como árvores, algumas partes da estrutura de dados pode ser "compartilhada", para que você não é obrigado a copiar tudo de cada vez.

Para obter mais informações por favor dê uma olhada neste papel: " Estruturas de dados puramente funcional " por Chris Okasaki.

"imutáveis" significa exatamente isso:. Isso não muda

A forma como os programas funcionais fazer atualizações é pela passagem em torno de coisas novas. Um valor existente nunca muda: você acabou de construir um novo valor e passar essa vez. Muitas vezes, as novas ações valor de estado com o antigo; bons exemplos da técnica são as listas de células compostas cons, e o zíper .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top