Por quê na programação funcional não ocorrem efeitos colaterais funcionais

Por quê na programação funcional não ocorrem efeitos colaterais funcionais


Eu sinto que os efeitos colaterais são um fenômeno natural. Mas é algo como tabu em linguagens funcionais. Quais são as razões?

Minha pergunta é específica para o estilo de programação funcional. Nem todas as linguagens / paradigmas de programação.



Respostas:


Escrever suas funções / métodos sem efeitos colaterais - para que sejam funções puras - facilita o raciocínio sobre a correção do seu programa.

Também facilita a composição dessas funções para criar um novo comportamento.

Também possibilita certas otimizações, nas quais o compilador pode, por exemplo, memorizar os resultados de funções ou usar a eliminação de subexpressão comum.

Edit: a pedido de Benjol: como muitos de seus estados estão armazenados na pilha (fluxo de dados, não fluxo de controle, como Jonas o chamou aqui ), você pode paralelizar ou reordenar a execução das partes de sua computação que são independentes de entre si. Você pode encontrar facilmente essas partes independentes porque uma parte não fornece entradas para a outra.

Em ambientes com depuradores que permitem reverter a pilha e retomar a computação (como o Smalltalk), ter funções puras significa que você pode ver facilmente como um valor muda, porque os estados anteriores estão disponíveis para inspeção. Em um cálculo com muita mutação, a menos que você inclua explicitamente ações de fazer / desfazer em sua estrutura ou algoritmo, não poderá ver o histórico da computação. (Isso está relacionado ao primeiro parágrafo: escrever funções puras facilita a inspeção da correção do seu programa.)







De um artigo sobre programação funcional :

Na prática, os aplicativos precisam ter alguns efeitos colaterais. Simon Peyton-Jones, um dos principais colaboradores da linguagem de programação funcional Haskell, disse o seguinte: "No final, qualquer programa deve manipular o estado. Um programa que não tem efeitos colaterais é uma espécie de caixa preta. Tudo o que você pode dizer é que a caixa fica mais quente ". ( http://oscon.blip.tv/file/324976 ) A chave é limitar os efeitos colaterais, identificá-los claramente e evitar dispersá-los por todo o código.



Você entendeu errado, a programação funcional promove efeitos colaterais limitantes para facilitar a compreensão e otimização dos programas. Até o Haskell permite gravar em arquivos.

Essencialmente, o que estou dizendo é que os programadores funcionais não acham os efeitos colaterais ruins, simplesmente acham que limitar o uso de efeitos colaterais é bom. Eu sei que pode parecer uma distinção tão simples, mas faz toda a diferença.







Algumas notas:

  • Funções sem efeitos colaterais podem ser executadas de maneira trivial em paralelo, enquanto funções com efeitos colaterais normalmente requerem algum tipo de sincronização.

  • Funções sem efeitos colaterais permitem uma otimização mais agressiva (por exemplo, usando um cache de resultados de forma transparente), porque, enquanto obtemos o resultado certo, nem importa se a função foi realmente executada ou não






Agora trabalho principalmente em código funcional e, dessa perspectiva, parece óbvio. Os efeitos colaterais criam um enorme fardo mental para os programadores que tentam ler e entender o código. Você não percebe esse fardo até ficar livre dele por um tempo e, de repente, precisa ler o código com efeitos colaterais novamente.

Considere este exemplo simples:

val foo = 42 // Several lines of code you don't really care about, but that contain a // lot of function calls that use foo and may or may not change its value // by side effect. // Code you are troubleshooting // What's the expected value of foo here?

Em uma linguagem funcional, eu sei que fooainda é 42. Nem preciso olhar para o código intermediário, muito menos entendê-lo, ou observar as implementações das funções que ele chama.

Todo esse material sobre concorrência, paralelização e otimização é bom, mas é isso que os cientistas da computação colocam no folheto. Não preciso me perguntar quem está mudando sua variável e quando é o que eu realmente gosto no dia-a-dia.


Poucos ou nenhum idioma tornam impossível causar efeitos colaterais. Idiomas completamente livres de efeitos colaterais seriam proibitivamente difíceis (quase impossíveis) de usar, exceto em uma capacidade muito limitada.

Por que efeitos colaterais são considerados maus?

Porque eles tornam muito mais difícil raciocinar sobre exatamente o que um programa faz e provar que ele faz o que você espera que ele faça.

Em um nível muito alto, imagine testar um site inteiro de três camadas com apenas testes de caixa preta. Claro, é factível, dependendo da escala. Mas certamente há muita duplicação acontecendo. E se não é um bug (que está relacionado a um efeito colateral), então você poderia quebrar todo o sistema para testes adicionais, até que o bug é diagnosticado e corrigido, ea correção é implantado no ambiente de teste.

Benefícios

Agora, reduza isso. Se você fosse bastante bom em escrever código livre de efeitos colaterais, quanto mais rápido seria em raciocinar sobre o que algum código existente fazia? Quanto mais rápido você poderia escrever testes de unidade? Como confiante você se sentiria que o código sem efeitos colaterais foi garantida livre de bugs, e que os usuários podem limitar a sua exposição a todos os erros que se têm?

Se o código não tiver efeitos colaterais, o compilador também poderá ter otimizações adicionais que ele poderá executar. Pode ser muito mais fácil implementar essas otimizações. Pode ser muito mais fácil conceber uma otimização para código livre de efeitos colaterais, o que significa que o fornecedor do compilador pode implementar otimizações que são difíceis de impossíveis no código com efeitos colaterais.

A simultaneidade também é drasticamente mais simples de implementar, gerar automaticamente e otimizar quando o código não tem efeitos colaterais. Isso ocorre porque todas as peças podem ser avaliadas com segurança em qualquer ordem. Permitir que os programadores escrevam códigos altamente concorrentes é amplamente considerado o próximo grande desafio que a Ciência da Computação precisa enfrentar e um dos poucos hedges restantes contra a Lei de Moore .




Os efeitos colaterais são como "vazamentos" em seu código que precisarão ser tratados posteriormente, por você ou por algum colega de trabalho desavisado.

Linguagens funcionais evitam variáveis ​​de estado e dados mutáveis ​​como uma maneira de tornar o código menos dependente do contexto e mais modular. A modularidade garante que o trabalho de um desenvolvedor não afete / prejudique o trabalho de outro.

Escalar a taxa de desenvolvimento com o tamanho da equipe é hoje um "santo graal" do desenvolvimento de software. Ao trabalhar com outros programadores, poucas coisas são tão importantes quanto a modularidade. Até os efeitos colaterais lógicos mais simples tornam a colaboração extremamente difícil.





Bem, IMHO, isso é bastante hipócrita. Ninguém gosta de efeitos colaterais, mas todo mundo precisa deles.

O que é tão perigoso quanto aos efeitos colaterais é que, se você chamar uma função, isso possivelmente terá um efeito não apenas no modo como a função se comportará quando for chamada da próxima vez, mas possivelmente terá esse efeito em outras funções. Assim, os efeitos colaterais introduzem um comportamento imprevisível e dependências não triviais.

Paradigmas de programação, como OO e funcional, abordam esse problema. OO reduz o problema impondo uma separação de preocupações. Isso significa que o estado do aplicativo, que consiste em muitos dados mutáveis, é encapsulado em objetos, cada um dos quais é responsável por manter apenas seu próprio estado. Dessa forma, o risco de dependências é reduzido e os problemas são muito mais isolados e fáceis de rastrear.

A programação funcional adota uma abordagem muito mais radical, onde o estado do aplicativo é simplesmente imutável da perspectiva do programador. É uma boa idéia, mas torna o idioma inútil por si só. Por quê? Porque QUALQUER operação de E / S tem efeitos colaterais. Assim que você ler de qualquer fluxo de entrada, é provável que o estado do aplicativo seja alterado, porque na próxima vez que você chamar a mesma função, é provável que o resultado seja diferente. Você pode estar lendo dados diferentes ou - também uma possibilidade - a operação pode falhar. O mesmo vale para a saída. Saída uniforme é uma operação com efeitos colaterais. Isso não é nada que você percebe hoje em dia, mas imagine que você tem apenas 20K para sua saída e, se produzir mais, seu aplicativo trava porque você está sem espaço em disco ou o que quer.

Então, sim, os efeitos colaterais são desagradáveis ​​e perigosos da perspectiva de um programador. A maioria dos bugs vem do modo como certas partes do estado do aplicativo são interligadas de maneira quase obscura, por meio de efeitos colaterais não considerados e muitas vezes desnecessários. Na perspectiva de um usuário, efeitos colaterais são o ponto de usar um computador. Eles não se importam com o que acontece dentro ou como é organizado. Eles fazem algo e esperam que o computador mude de acordo.





Qualquer efeito colateral introduz parâmetros extras de entrada / saída que devem ser levados em consideração durante o teste.

Isso torna a validação do código muito mais complexa, pois o ambiente não pode ser limitado apenas ao código que está sendo validado, mas deve trazer parte ou todo o ambiente circundante (o global que é atualizado vive nesse código ali, que por sua vez depende disso código, que por sua vez depende de viver dentro de um servidor Java EE completo ....)

Ao tentar evitar efeitos colaterais, você limita a quantidade de externalismo necessária para executar o código.


Na minha experiência, um bom design na programação orientada a objetos exige o uso de funções que têm efeitos colaterais.

Por exemplo, considere um aplicativo básico da interface do usuário da área de trabalho. Talvez eu tenha um programa em execução que tenha em sua pilha um gráfico de objetos representando o estado atual do modelo de domínio do meu programa. As mensagens chegam aos objetos nesse gráfico (por exemplo, através de chamadas de métodos chamadas do controlador de camada da interface do usuário). O gráfico do objeto (modelo de domínio) na pilha é modificado em resposta às mensagens. Os observadores do modelo são informados sobre quaisquer alterações, a interface do usuário e talvez outros recursos sejam modificados.

Longe de ser ruim, o arranjo correto desses efeitos colaterais que modificam a pilha e a tela estão no centro do design do OO (neste caso, o padrão MVC).

Obviamente, isso não significa que seus métodos devam ter efeitos colaterais arbitrários. E as funções livres de efeito colateral têm um lugar na melhoria da legibilidade e, às vezes, do desempenho do seu código.




O mal é um pouco exagerado. Tudo depende do contexto do uso da linguagem.

Outra consideração aos já mencionados é que torna as provas de correção de um programa muito mais simples se não houver efeitos colaterais funcionais.


Como as perguntas acima apontaram, as linguagens funcionais não impedem tanto o código de causar efeitos colaterais, como fornecem ferramentas para gerenciar quais efeitos colaterais podem acontecer em um determinado pedaço de código e quando.

Isso acaba tendo consequências muito interessantes. Primeiro, e mais obviamente, existem inúmeras coisas que você pode fazer com o código livre de efeitos colaterais, que já foi descrito. Mas há outras coisas que também podemos fazer, mesmo ao trabalhar com código que tenha efeitos colaterais:

  • Em código com estado mutável, podemos gerenciar o escopo do estado de forma a garantir estaticamente que ele não possa vazar para fora de uma determinada função, o que nos permite coletar lixo sem os esquemas de contagem de referência ou de estilo de marcação e varredura , ainda assim, verifique se nenhuma referência sobrevive. As mesmas garantias também são úteis para manter informações confidenciais, etc. (Isso pode ser obtido usando a mônada ST em haskell)
  • Ao modificar o estado compartilhado em vários encadeamentos, podemos evitar a necessidade de bloqueios rastreando alterações e executando uma atualização atômica no final de uma transação, ou revertendo a transação e repetindo-a se outro encadeamento fizer uma modificação conflitante. Isso só é possível porque podemos garantir que o código não tenha outros efeitos além das modificações de estado (que podemos abandonar alegremente). Isso é realizado pela mônada do STM (Software Transactional Memory) em Haskell.
  • podemos rastrear os efeitos do código e colocá-lo em uma caixa de areia trivial, filtrando os efeitos que ele possa precisar para garantir sua segurança, permitindo, por exemplo, que o código digitado pelo usuário seja executado com segurança em um site


Em bases de código complexas, interações complexas de efeitos colaterais são a coisa mais difícil sobre a qual acho. Só posso falar pessoalmente, dada a forma como meu cérebro funciona. Efeitos colaterais, estados persistentes, entradas mutantes e assim por diante me fazem pensar sobre "quando" e "onde" as coisas raciocinam sobre a correção, e não apenas "o que" está acontecendo em cada função individual.

Não consigo me concentrar apenas no "o quê". Não posso concluir, depois de testar minuciosamente uma função que causa efeitos colaterais, que espalhe um ar de confiabilidade por todo o código que a utiliza, pois os chamadores ainda podem abusar dela chamando-a na hora errada, no encadeamento errado, no errado ordem. Enquanto isso, uma função que não causa efeitos colaterais e apenas retorna uma nova saída dada uma entrada (sem tocar na entrada) é praticamente impossível de usar dessa maneira.

Mas eu sou do tipo pragmático, acho, ou pelo menos tento ser, e não acho que tenhamos necessariamente de eliminar todos os efeitos colaterais ao mínimo possível para raciocinar sobre a correção do nosso código (no mínimo Eu acharia isso muito difícil de fazer em idiomas como C). Onde acho muito difícil argumentar sobre a correção é quando temos a combinação de fluxos de controle complexos e efeitos colaterais.

Fluxos de controle complexos para mim são os de natureza gráfica, geralmente recursivos ou recursivos (filas de eventos, por exemplo, que não estão chamando diretamente os eventos recursivamente, mas são "recursivos" na natureza), talvez fazendo coisas no processo de atravessar uma estrutura de gráfico vinculada real ou processar uma fila de eventos não homogênea que contém uma mistura eclética de eventos a serem processados, levando-nos a todos os tipos de partes diferentes da base de código e a todos os efeitos colaterais diferentes. Se você tentasse desenhar todos os lugares em que acabaria no código, ele se pareceria com um gráfico complexo e, potencialmente, com nós no gráfico que você nunca imaginou que estariam lá naquele momento, e considerando que eles são todos causando efeitos colaterais,

As linguagens funcionais podem ter fluxos de controle extremamente complexos e recursivos, mas o resultado é tão fácil de entender em termos de correção, porque não existem todos os tipos de efeitos colaterais ecléticos ocorrendo no processo. É somente quando fluxos de controle complexos encontram efeitos colaterais ecléticos que considero indutor de dor de cabeça tentar compreender a totalidade do que está acontecendo e se sempre faz a coisa certa.

Portanto, quando tenho esses casos, geralmente acho muito difícil, se não impossível, me sentir muito confiante sobre a correção de tal código, muito menos muito confiante de que posso fazer alterações nesse código sem tropeçar em algo inesperado. Portanto, a solução para mim é simplificar o fluxo de controle ou minimizar / unificar os efeitos colaterais (ao unificar, quero dizer, causar apenas um tipo de efeito colateral a muitas coisas durante uma fase específica do sistema, não dois, três ou três). dúzia). Eu preciso que uma dessas duas coisas aconteça para permitir que meu cérebro simplório se sinta confiante sobre a correção do código que existe e a correção das alterações que apresento. É muito fácil ter certeza da correção do código que introduz efeitos colaterais, se os efeitos colaterais são uniformes e simples, juntamente com o fluxo de controle, da seguinte forma:

for each pixel in an image: make it red

É muito fácil argumentar sobre a correção desse código, mas principalmente porque os efeitos colaterais são muito uniformes e o fluxo de controle é muito simples. Mas digamos que tivemos um código como este:

for each vertex to remove in a mesh: start removing vertex from connected edges(): start removing connected edges from connected faces(): rebuild connected faces excluding edges to remove(): if face has less than 3 edges: remove face remove edge remove vertex

Então, esse é um pseudocódigo ridiculamente simplificado, que normalmente envolveria muito mais funções e loops aninhados e muito mais coisas que teriam que continuar (atualizando vários mapas de textura, pesos ósseos, estados de seleção etc.), mas mesmo o pseudocódigo torna tão difícil razão da correção por causa da interação do fluxo de controle complexo tipo gráfico e dos efeitos colaterais que estão ocorrendo. Portanto, uma estratégia para simplificar é adiar o processamento e focar apenas em um tipo de efeito colateral por vez:

for each vertex to remove: mark connected edges for each marked edge: mark connected faces for each marked face: remove marked edges from face if num_edges < 3: remove face for each marked edge: remove edge for each vertex to remove: remove vertex

... algo nesse sentido como uma iteração de simplificação. Isso significa que estamos passando os dados várias vezes, o que definitivamente gera um custo computacional, mas geralmente descobrimos que podemos multithread desse código resultante com mais facilidade, agora que os efeitos colaterais e os fluxos de controle assumiram essa natureza uniforme e mais simples. Além disso, cada loop pode ser mais amigável ao cache do que atravessar o gráfico conectado e causar efeitos colaterais à medida que avançamos (por exemplo: use um conjunto de bits paralelos para marcar o que precisa ser atravessado, para que possamos fazer as passagens diferidas em ordem sequencial classificada usando bitmasks e FFS). Mas o mais importante é que acho a segunda versão muito mais fácil de raciocinar em termos de correção, além de alterar sem causar bugs. De modo a'

E, afinal, precisamos que efeitos colaterais ocorram em algum momento, ou então teríamos apenas funções que geram dados sem ter para onde ir. Frequentemente, precisamos gravar algo em um arquivo, exibir algo em uma tela, enviar os dados por um soquete, algo desse tipo e todas essas coisas são efeitos colaterais. Mas podemos definitivamente reduzir o número de efeitos colaterais supérfluos que ocorrem e também reduzir o número de efeitos colaterais quando os fluxos de controle são muito complicados, e acho que seria muito mais fácil evitar erros se o fizéssemos.


Isso não é mau. Na minha opinião, é necessário distinguir os dois tipos de funções - com efeitos colaterais e sem. A função sem efeitos colaterais: - retorna sempre o mesmo com os mesmos argumentos; portanto, por exemplo, essa função sem argumentos não faz sentido. - Isso também significa que a ordem na qual algumas dessas funções são chamadas não desempenha nenhum papel - deve poder ser executada e pode ser depurada apenas (!), Sem nenhum outro código. E agora, lol, veja o que o JUnit faz. Uma função com efeitos colaterais: - possui uma espécie de "vazamentos", o que pode ser destacado automaticamente - é muito importante depurando e procurando erros, o que geralmente é causado por efeitos colaterais. - Qualquer função com efeitos colaterais também possui uma "parte" de si mesma sem efeitos colaterais, o que também pode ser separado automaticamente. Tão maus são esses efeitos colaterais,


O que e efeito colateral em programação funcional?

Um efeito colateral é qualquer alteração no estado da aplicação que seja percebida fora do escopo da função chamada. Isso pode gerar alguns problemas como imprevisibilidade e complexidade no raciocínio.

Qual a principal característica da programação funcional?

Em ciência da computação, programação funcional é um paradigma de programação que trata a computação como uma avaliação de funções matemáticas e que evita estados ou dados mutáveis. Ela enfatiza a aplicação de funções, em contraste da programação imperativa, que enfatiza mudanças no estado do programa.

Qual e a vantagem de se utilizar o paradigma de programação funcional?

– Fácil manutenção; – Processamento em paralelo; – Facilidade nos testes e na busca por bugs; – Caminho para se pensar de forma funcional é mais complexo para quem já programou em linguagens imperativas.

Qual a diferença entre programação funcional e orientada a objetos?

Enquanto a Programação Funcional é baseada em Funcçoes matemáticas ou seja Calculos onde um conjuto faz parte de um todo, a OOP se baseia e, formas e comportamentos de uma Objeto utilizando-se de campos e Métodos para os mesmos.