A programação funcional inclui muitas técnicas diferentes. Algumas técnicas são boas com efeitos colaterais. Mas um aspecto importante é o raciocínio equacional : se eu chamo uma função com o mesmo valor, sempre obtenho o mesmo resultado. Portanto, posso substituir uma chamada de função pelo valor de retorno e obter um comportamento equivalente. Isso facilita o raciocínio sobre o programa, especialmente durante a depuração.
Caso a função tenha efeitos colaterais, isso não se aplica. O valor de retorno não é equivalente à chamada da função, porque o valor de retorno não contém os efeitos colaterais.
A solução é parar de usar efeitos colaterais e codificá-los no valor de retorno . Idiomas diferentes têm sistemas de efeitos diferentes. Por exemplo, Haskell usa mônadas para codificar certos efeitos, como IO ou mutação de estado. As linguagens C / C ++ / Rust possuem um sistema de tipos que pode impedir a mutação de alguns valores.
Em uma linguagem imperativa, uma print("foo")
função imprimirá algo e não retornará nada. Em uma linguagem funcional pura como Haskell, uma print
função também pega um objeto que representa o estado do mundo exterior e retorna um novo objeto que representa o estado depois de executar essa saída. Algo parecido com newState = print "foo" oldState
. Eu posso criar quantos estados novos quiser do estado antigo. No entanto, apenas um será usado pela função principal. Então, preciso sequenciar os estados de várias ações, encadeando as funções. Para imprimir foo bar
, posso dizer algo como print "bar" (print "foo" originalState)
.
Se um estado de saída não for usado, o Haskell não executará as ações anteriores àquele estado, porque é uma linguagem lenta. Por outro lado, essa preguiça só é possível porque todos os efeitos são explicitamente codificados como valores de retorno.
Observe que Haskell é a única linguagem funcional comumente usada que usa essa rota. Outras linguagens funcionais incl. a família Lisp, a família ML e as linguagens funcionais mais recentes, como Scala, desencorajam, mas permitem efeitos colaterais ainda - elas podem ser chamadas de linguagens funcionais imperativas.
Usar efeitos colaterais para E / S provavelmente é bom. Freqüentemente, a E / S (exceto a criação de log) é feita apenas no limite externo do seu sistema. Nenhuma comunicação externa acontece dentro da sua lógica de negócios. Em seguida, é possível escrever o núcleo do seu software em um estilo puro, enquanto ainda executa E / S impuras em um shell externo. Isso também significa que o núcleo pode ser sem estado.
A apatridia possui várias vantagens práticas, como maior razoabilidade e escalabilidade. Isso é muito popular para back-end de aplicativos da web. Qualquer estado é mantido fora, em um banco de dados compartilhado. Isso facilita o balanceamento de carga: não preciso colar sessões em um servidor específico. E se eu precisar de mais servidores? Basta adicionar outro, porque ele está usando o mesmo banco de dados. E se um servidor travar? Posso refazer qualquer solicitação pendente em outro servidor. Claro, ainda há estado - no banco de dados. Mas eu o expliquei e extraí, e poderia usar uma abordagem funcional pura internamente, se eu quisesse.
Nenhuma linguagem de programação elimina efeitos colaterais. Eu acho que é melhor dizer que linguagens declarativas contêm efeitos colaterais, enquanto linguagens imperativas não. No entanto, não tenho tanta certeza de que alguma dessas conversas sobre efeitos colaterais chegue à diferença fundamental entre os dois tipos de idiomas e que realmente pareça o que você está procurando.
Eu acho que ajuda a ilustrar a diferença com um exemplo.
A linha de código acima pode ser escrita em praticamente qualquer idioma. Como podemos determinar se estamos usando uma linguagem imperativa ou declarativa? Como as propriedades dessa linha de código são diferentes nas duas classes de linguagem?
Em uma linguagem imperativa (C, Java, Javascript etc.), essa linha de código representa apenas uma etapa do processo. Não nos diz nada sobre a natureza fundamental de qualquer um dos valores. Ele nos diz que, no momento seguinte a esta linha de código (mas antes da próxima linha),
a
será igual ab
mais,c
mas não nos diz nadaa
no sentido mais amplo.Em uma linguagem declarativa (Haskell, Scheme, Excel, etc.), essa linha de código diz muito mais. Ele estabelece uma relação invariável entre
a
e os outros dois objetos, de modo que sempre será o casoa
igual ab
maisc
. Note, que incluiu Excel na lista de linguagens declarativas porque, mesmo seb
ouc
altera o valor, o fato de que ainda permanecem quea
será igual à sua soma.A meu ver , isso não é efeitos colaterais ou estado, é o que diferencia os dois tipos de idiomas. Em uma linguagem imperativa, qualquer linha de código específica não diz nada sobre o significado geral das variáveis em questão. Em outras palavras,
a = b + c
significa apenas que, por um breve momento,a
igualou a soma deb
ec
.Enquanto isso, em linguagens declarativas, toda linha de código estabelece uma verdade fundamental que existirá durante toda a vida útil do programa. Nessas linguagens,
a = b + c
informa que, não importa o que aconteça em qualquer outra linha de códigoa
, sempre será igual à soma deb
ec
.fonte