Si ma fonction répond à moins de deux exigences, je crois que la fonction Sum
pour renvoyer la somme des éléments dans une liste où l'élément est évalué à vrai pour une condition donnée peut être appelée fonction pure, n'est-ce pas?
1) Pour un ensemble donné de i / p, le même o / p est renvoyé quel que soit le moment où la fonction est appelée
2) Il n'a aucun effet secondaire
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;
}
Exemple : Sum(x=>x%2==0, new List<int> {1,2,3,4,5...100});
La raison pour laquelle je pose cette question est parce que je vois presque partout où les gens conseillent d'éviter les opérateurs d'affectation et les boucles car c'est un style de programmation impératif. Alors, qu'est-ce qui peut mal tourner avec l'exemple ci-dessus qui utilise des boucles et un opérateur d'affectation dans le contexte de la programmation de fonctions?
la source
item
variable mute dans la boucle.Réponses:
Qu'est-ce qui fait la différence dans la programmation fonctionnelle?
La programmation fonctionnelle est par principe déclarative . Vous dites quel est votre résultat au lieu de savoir comment le calculer.
Jetons un coup d'œil à l'implémentation vraiment fonctionnelle de votre extrait. À Haskell, ce serait:
Est - il clair que le résultat est? Tout à fait ainsi, c'est la somme des nombres rencontrant le prédicat. Comment est-il calculé? Je m'en fiche, demandez au compilateur.
Vous pourriez peut-être dire que l'utilisation de
sum
etfilter
est une astuce et cela ne compte pas. Laissez-le ensuite implémenter sans ces aides (bien que la meilleure façon serait de les implémenter en premier).La solution "Functional Programming 101" qui n'utilise pas
sum
est à récursivité:Il est encore assez clair quel est le résultat en termes d'appel de fonction unique. C'est soit
0
, soitrecursive call + h or 0
, selonpred h
. Toujours assez simple, même si le résultat final n'est pas immédiatement évident (bien qu'avec un peu de pratique, cela se lit vraiment comme unefor
boucle).Comparez cela à votre version:
Quel est le résultat? Oh, je vois: seule
return
déclaration, pas de surprise ici:return result
.Mais c'est quoi
result
?int result = 0
? Cela ne semble pas correct. Vous faites quelque chose plus tard avec ça0
. Ok, vous y ajoutez desitem
s. Etc.Bien sûr, pour la plupart des programmeurs, c'est assez évident ce qui se passe dans une fonction simple comme celle-ci, mais ajoutez une
return
instruction supplémentaire ou plus et il devient soudainement plus difficile à suivre. Tout le code consiste à savoir comment et ce qu'il reste au lecteur à comprendre - c'est clairement un style très impératif .Alors, les variables et les boucles sont-elles incorrectes?
Non.
Il y a beaucoup de choses qui sont beaucoup plus faciles à expliquer par eux, et de nombreux algorithmes qui nécessitent un état mutable pour être rapide. Mais les variables sont intrinsèquement impératives, expliquant comment au lieu de quoi , et donnant peu de prédiction de ce que leur valeur peut être quelques lignes plus tard ou après quelques itérations de boucle. Les boucles nécessitent généralement un état pour avoir un sens, et elles sont donc également intrinsèquement impératives.
Les variables et les boucles ne sont tout simplement pas une programmation fonctionnelle.
Sommaire
La programmation fonctionnellement contemporaine est un peu plus de style et une façon de penser utile qu'un paradigme. Une forte préférence pour les fonctions pures est dans cet état d'esprit, mais ce n'est qu'une petite partie en fait.
La plupart des langages répandus vous permettent d'utiliser certaines constructions fonctionnelles. Par exemple en Python, vous pouvez choisir entre:
ou
ou
Ces expressions fonctionnelles conviennent parfaitement à ce genre de problèmes et rendent simplement le code plus court (et plus court est bon ). Vous ne devriez pas remplacer inconsidérément le code impératif par eux, mais lorsqu'ils conviennent, ils sont presque toujours un meilleur choix.
la source
L'utilisation de l'état mutable est généralement déconseillée dans la programmation fonctionnelle. Les boucles sont découragées en conséquence, car les boucles ne sont utiles qu'en combinaison avec un état mutable.
La fonction dans son ensemble est pure, ce qui est formidable, mais le paradigme de la programmation fonctionnelle ne s'applique pas seulement au niveau des fonctions entières. Vous voulez également éviter l'état mutable également au niveau local, dans les fonctions. Et le raisonnement est fondamentalement le même: éviter l'état mutable rend le code plus facile à comprendre et empêche certains bogues.
Dans votre cas, vous pourriez écrire
numbers.Where(predicate).Sum()
ce qui est clairement beaucoup plus simple. Et plus simple signifie moins de bugs.la source
Where
innumbers.Where(predicate).Sum()
- il utilise laforeach
boucle.Bien que vous ayez raison de dire que du point de vue d'un observateur externe, votre
Sum
fonction est pure, l'implémentation interne n'est clairement pas pure - vous avez un état stocké dansresult
lequel vous mutez à plusieurs reprises. L'une des raisons pour éviter un état mutable est qu'il produit une plus grande charge cognitive sur le programmeur, ce qui conduit à son tour à plus de bogues [citation nécessaire] .Alors que dans un exemple simple comme celui-ci, la quantité d'état mutable stockée est probablement suffisamment petite pour ne pas causer de problèmes graves, le principe général s'applique toujours. Un exemple de jouet comme
Sum
probablement n'est pas le meilleur moyen d'illustrer l'avantage de la programmation fonctionnelle par rapport à l'impératif - essayez de faire quelque chose avec beaucoup d'état mutable et les avantages peuvent devenir plus clairs.la source