Se minha função atender abaixo de dois requisitos, acredito que a função Sum
retorne a soma dos itens em uma lista onde o item é avaliado como verdadeiro para uma determinada condição qualificada para ser referida como função pura, não é?
1) Para um determinado conjunto de i / p, o mesmo o / p é retornado independentemente do tempo em que a função é chamada
2) Não tem nenhum efeito colateral
public int Sum(Func<int,bool> predicate, IEnumerable<int> numbers){
int result = 0;
foreach(var item in numbers)
if(predicate(item)) result += item;
return result;
}
Exemplo: Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});
A razão pela qual estou fazendo essa pergunta é porque vejo quase todos os lugares onde as pessoas recomendam evitar o operador de atribuição e os loops, porque é um estilo de programação imperativo. Então, o que pode dar errado no exemplo acima, que utiliza loops e operador de atribuição no contexto da programação de funções?
fonte
item
variável sofre mutação no loop.Respostas:
O que há na programação funcional que faz a diferença?
A programação funcional é, por princípio, declarativa . Você diz qual é o seu resultado, em vez de como calculá-lo.
Vamos dar uma olhada na implementação realmente funcional do seu snippet. Em Haskell, seria:
Está claro qual é o resultado? Bastante, é a soma dos números que atendem ao predicado. Como é calculado? Eu não ligo, pergunte ao compilador.
Você poderia dizer que usar
sum
efilter
é um truque e não conta. Vamos implementá-lo sem esses ajudantes (embora a melhor maneira seja implementá-los primeiro).A solução "Functional Programming 101" que não usa
sum
é com recursão:Ainda está bem claro qual é o resultado em termos de chamada de função única. É
0
ourecursive call + h or 0
, dependendopred h
. Ainda bem direto, mesmo que o resultado final não seja imediatamente óbvio (embora com um pouco de prática isso realmente pareça umfor
loop).Compare isso com a sua versão:
Qual é o resultado? Oh, eu vejo: única
return
declaração, sem surpresas aqui:return result
.Mas o que é isso
result
?int result = 0
? Não parece certo. Você faz algo mais tarde com isso0
. Ok, você adicionaitem
s a ele. E assim por diante.É claro que, para a maioria dos programadores, isso é bastante óbvio, o que acontece em uma função simples como essa, mas adicione uma
return
declaração extra ou algo assim e de repente fica mais difícil de rastrear. Todo o código é sobre como e o que resta para o leitor descobrir - esse é claramente um estilo muito imperativo .Então, as variáveis e os loops estão errados?
Não.
Há muitas coisas que são muito mais fáceis de serem explicadas por eles e muitos algoritmos que exigem que o estado mutável seja rápido. Porém, as variáveis são inerentemente imperativas, explicando como, em vez de o quê , e dando pouca previsão de qual pode ser seu valor algumas linhas depois ou após algumas iterações de loop. Os loops geralmente exigem que o estado faça sentido e, portanto, são inerentemente imperativos.
Variáveis e loops simplesmente não são programação funcional.
Sumário
A programação funcional contemporânea é um pouco mais de estilo e um modo de pensar útil do que um paradigma. A forte preferência pelas funções puras está nessa mentalidade, mas na verdade é apenas uma pequena parte.
A maioria das linguagens comuns permite que você use algumas construções funcionais. Por exemplo, em Python, você pode escolher entre:
ou
ou
Essas expressões funcionais são adequadas para problemas desse tipo e simplesmente tornam o código mais curto (e menor é bom ). Você não deve substituir pensativamente o código imperativo por eles, mas quando eles se encaixam, eles quase sempre são uma escolha melhor.
fonte
O uso do estado mutável geralmente é desencorajado na programação funcional. Os loops são desencorajados como conseqüência, porque os loops são úteis apenas em combinação com o estado mutável.
A função como um todo é pura, o que é ótimo, mas o paradigma da programação funcional não se aplica apenas ao nível de funções inteiras. Você também deseja evitar o estado mutável também no nível local, dentro das funções. E o raciocínio é basicamente o mesmo: evitar o estado mutável facilita a compreensão do código e evita certos erros.
No seu caso, você poderia escrever o
numbers.Where(predicate).Sum()
que é claramente muito mais simples. E mais simples significa menos erros.fonte
Where
nonumbers.Where(predicate).Sum()
- ele faz uso deforeach
loop.Enquanto você está certo de que, do ponto de vista de um observador externo, sua
Sum
função é pura, a implementação interna claramente não é pura - você tem um estado armazenado noresult
qual você muda repetidamente. Uma das razões para evitar estado mutável é porque produz uma maior carga cognitiva sobre o programador, que por sua vez leva a mais erros [carece de fontes?] .Embora em um exemplo simples como esse, a quantidade de estado mutável sendo armazenada seja provavelmente pequena o suficiente para não causar problemas sérios, o princípio geral ainda se aplica. Um exemplo de brinquedo como
Sum
provavelmente não é a melhor maneira de ilustrar a vantagem da programação funcional sobre o imperativo - tente fazer algo com muito estado mutável e as vantagens podem ficar mais claras.fonte