Recentemente, aprendi sobre programação funcional (especificamente Haskell, mas também passei por tutoriais sobre Lisp e Erlang). Embora tenha achado os conceitos muito esclarecedores, ainda não vejo o lado prático do conceito "sem efeitos colaterais". Quais são as vantagens práticas disso? Estou tentando pensar na mentalidade funcional, mas há algumas situações que parecem muito complexas sem a capacidade de salvar o estado de uma maneira fácil (não considero as mônadas de Haskell 'fáceis').
Vale a pena continuar aprendendo Haskell (ou outra linguagem puramente funcional) em profundidade? A programação funcional ou sem estado é realmente mais produtiva do que processual? É provável que continuarei a usar o Haskell ou outra linguagem funcional posteriormente, ou devo aprender apenas para o entendimento?
Preocupo-me menos com desempenho do que com produtividade. Então, estou perguntando principalmente se serei mais produtivo em uma linguagem funcional do que em procedimentos / orientados a objetos / o que for.
fonte
Quanto mais partes do seu programa não tiverem estado, mais maneiras existem de juntar as peças sem quebrar nada . O poder do paradigma apátrida não reside na apatridia (ou pureza) per se , mas na capacidade que ele lhe dá de escrever funções poderosas e reutilizáveis e combiná-las.
Você pode encontrar um bom tutorial com muitos exemplos no artigo de John Hughes, Por que a Programação Funcional é Importante (PDF).
Você será gobs mais produtivo, especialmente se você escolher uma linguagem funcional, que também tem tipos de dados algébricos e correspondência de padrão (Caml, SML, Haskell).
fonte
Muitas das outras respostas se concentraram no lado do desempenho (paralelismo) da programação funcional, que acredito ser muito importante. No entanto, você perguntou especificamente sobre produtividade, como em: é possível programar a mesma coisa mais rapidamente em um paradigma funcional do que em um paradigma imperativo.
Na verdade, acho (por experiência pessoal) que a programação em F # corresponde à maneira como penso melhor e, portanto, é mais fácil. Eu acho que é a maior diferença. Eu programei em F # e C #, e há muito menos "lutando contra a linguagem" em F #, que eu amo. Você não precisa pensar nos detalhes em F #. Aqui estão alguns exemplos do que eu realmente gostei.
Por exemplo, mesmo que o F # seja digitado estaticamente (todos os tipos são resolvidos em tempo de compilação), a inferência de tipo descobre quais tipos você possui, para que você não precise dizer isso. E se não conseguir descobrir, ele automaticamente torna sua função / classe / qualquer que seja genérico. Portanto, você nunca precisa escrever nenhum genérico, tudo é automático. Acho que isso significa que estou gastando mais tempo pensando no problema e menos em como implementá-lo. De fato, sempre que volto ao C #, sinto muita falta dessa inferência de tipo, você nunca percebe o quão perturbador é até que não precise mais fazer isso.
Também em F #, em vez de escrever loops, você chama funções. É uma mudança sutil, mas significativa, porque você não precisa mais pensar na construção do loop. Por exemplo, aqui está um código que corresponderia a algo (não me lembro o quê, é de um quebra-cabeça de Euler do projeto):
Sei que fazer um filtro e depois um mapa (que é uma conversão de cada elemento) em C # seria bastante simples, mas é preciso pensar em um nível inferior. Particularmente, você teria que escrever o loop em si e ter sua própria declaração if explícita e esse tipo de coisa. Desde que aprendi F #, percebi que achei mais fácil codificar da maneira funcional. Onde, se você deseja filtrar, escreve "filter" e, se deseja mapear, escreve "map", em vez de implementar cada um dos detalhes.
Também adoro o operador |>, que acho que separa o F # do ocaml e possivelmente outras linguagens funcionais. É o operador de pipe, permite "canalizar" a saída de uma expressão na entrada de outra expressão. Faz o código seguir como penso mais. Como no snippet de código acima, está dizendo: "pegue a sequência de fatores, filtre-a e mapeie-a". É um nível de pensamento muito alto, que você não consegue em uma linguagem de programação imperativa porque está muito ocupado escrevendo o loop e as declarações if. É a única coisa que mais sinto falta sempre que vou para outro idioma.
Portanto, em geral, mesmo que eu possa programar em C # e F #, acho mais fácil usar o F # porque você pode pensar em um nível superior. Eu diria que, como os detalhes menores são removidos da programação funcional (pelo menos em F #), sou mais produtivo.
Edit : Eu vi em um dos comentários que você pediu um exemplo de "estado" em uma linguagem de programação funcional. F # pode ser escrito imperativamente, então aqui está um exemplo direto de como você pode ter um estado mutável em F #:
fonte
a |> b (p1, p2)
é apenas açúcar sintático parab (a, p1, p2)
. Junte isso à associatividade correta e você terá.Considere todos os erros difíceis que você passou muito tempo depurando.
Agora, quantos desses erros foram causados por "interações não desejadas" entre dois componentes separados de um programa? (Quase todos os bugs de encadeamento têm este formato: corridas envolvendo a gravação de dados compartilhados, deadlocks, ... Além disso, é comum encontrar bibliotecas que tenham algum efeito inesperado no estado global, ou ler / gravar o registro / ambiente, etc.) I afirmaria que pelo menos 1 em cada 3 'hard bugs' se enquadram nessa categoria.
Agora, se você mudar para a programação sem estado / imutável / pura, todos esses erros desaparecem. Você é apresentado com alguns novos desafios em vez (por exemplo, quando você faz querer diferentes módulos para interagir com o ambiente), mas em uma linguagem como Haskell, essas interações se explicitamente reificada no sistema tipo, o que significa que você pode apenas olhar para o tipo de uma função e uma razão sobre o tipo de interações que ele pode ter com o restante do programa.
Essa é a grande vitória da IMO de 'imutabilidade'. Em um mundo ideal, todos nós projetamos APIs fantásticas e, mesmo quando as coisas eram mutáveis, os efeitos seriam locais e bem documentados e as interações 'inesperadas' seriam reduzidas ao mínimo. No mundo real, existem muitas APIs que interagem com o estado global de inúmeras maneiras, e essas são a fonte dos erros mais perniciosos. Aspirar à apatridia é aspirar a se livrar de interações não intencionais / implícitas / nos bastidores entre os componentes.
fonte
Uma vantagem das funções sem estado é que elas permitem o pré-cálculo ou o armazenamento em cache dos valores de retorno da função. Mesmo alguns compiladores C permitem marcar explicitamente as funções como sem estado para melhorar sua otimização. Como muitos outros observaram, funções sem estado são muito mais fáceis de paralelizar.
Mas a eficiência não é a única preocupação. Uma função pura é mais fácil de testar e depurar, pois qualquer coisa que a afeta é explicitamente declarada. E, ao programar em uma linguagem funcional, o hábito de tornar o mínimo possível de funções "sujas" (com E / S, etc.). Separar as coisas com estado dessa maneira é uma boa maneira de criar programas, mesmo em linguagens não tão funcionais.
Os idiomas funcionais podem demorar um pouco para "chegar", e é difícil explicar para alguém que não passou por esse processo. Mas a maioria das pessoas que persiste por tempo suficiente finalmente percebe que a confusão vale a pena, mesmo que não acabem usando muito as linguagens funcionais.
fonte
sin(PI/3)
em seu código, onde PI é uma constante, o compilador possa avaliar essa função em tempo de compilação e incorporar o resultado no código gerado.Sem estado, é muito fácil paralelizar automaticamente seu código (como as CPUs são feitas com mais e mais núcleos, isso é muito importante).
fonte
Aplicativos da web sem estado são essenciais quando você começa a ter um tráfego maior.
Pode haver muitos dados do usuário que você não deseja armazenar no lado do cliente por motivos de segurança, por exemplo. Nesse caso, você precisa armazená-lo no lado do servidor. Você pode usar a sessão padrão dos aplicativos da web, mas se tiver mais de uma instância do aplicativo, precisará garantir que cada usuário esteja sempre direcionado para a mesma instância.
Os balanceadores de carga geralmente têm a capacidade de ter 'sessões fixas', onde o balanceador de carga sabe como para qual servidor enviar os usuários. Porém, isso não é ideal; por exemplo, significa que toda vez que você reiniciar o aplicativo da Web, todos os usuários conectados perderão a sessão.
Uma abordagem melhor é armazenar a sessão atrás dos servidores Web em algum tipo de armazenamento de dados; atualmente, existem muitos produtos nosql disponíveis para isso (redis, mongo, elasticsearch, memcached). Dessa forma, os servidores web não têm estado, mas você ainda possui o estado do servidor e a disponibilidade desse estado pode ser gerenciada escolhendo a configuração correta do armazenamento de dados. Esses armazenamentos de dados geralmente têm uma grande redundância, portanto, quase sempre deve ser possível fazer alterações em seu aplicativo Web e até mesmo no armazenamento de dados sem afetar os usuários.
fonte
Escrevi um post sobre esse assunto há algum tempo: Sobre a importância da pureza .
fonte