La programmation fonctionnelle comprend de nombreuses techniques différentes. Certaines techniques sont bien avec des effets secondaires. Mais un aspect important est le raisonnement équationnel : si j'appelle une fonction sur la même valeur, j'obtiens toujours le même résultat. Je peux donc remplacer un appel de fonction par la valeur de retour et obtenir un comportement équivalent. Cela facilite la réflexion sur le programme, en particulier lors du débogage.
Si la fonction a des effets secondaires, cela ne tient pas tout à fait. La valeur de retour n'est pas équivalente à l'appel de fonction, car la valeur de retour ne contient pas les effets secondaires.
La solution consiste à cesser d'utiliser les effets secondaires et à encoder ces effets dans la valeur de retour . Différentes langues ont des systèmes d'effets différents. Par exemple, Haskell utilise des monades pour coder certains effets tels que IO ou la mutation d'état. Les langages C / C ++ / Rust ont un système de types qui peut interdire la mutation de certaines valeurs.
Dans un langage impératif, une print("foo")
fonction imprime quelque chose et ne renvoie rien. Dans un langage fonctionnel pur comme Haskell, une print
fonction prend également un objet représentant l'état du monde extérieur et renvoie un nouvel objet représentant l'état après avoir effectué cette sortie. Quelque chose de similaire à newState = print "foo" oldState
. Je peux créer autant de nouveaux états à partir de l'ancien que je veux. Cependant, un seul sera jamais utilisé par la fonction principale. J'ai donc besoin de séquencer les états de plusieurs actions en enchaînant les fonctions. Pour imprimer foo bar
, je pourrais dire quelque chose comme print "bar" (print "foo" originalState)
.
Si un état de sortie n'est pas utilisé, Haskell n'exécute pas les actions menant à cet état, car il s'agit d'un langage paresseux. Inversement, cette paresse n'est possible que parce que tous les effets sont explicitement codés en tant que valeurs de retour.
Notez que Haskell est le seul langage fonctionnel couramment utilisé qui utilise cette route. Autres langages fonctionnels incl. la famille Lisp, la famille ML et les langages fonctionnels plus récents comme Scala découragent mais permettent des effets secondaires encore - ils pourraient être appelés langages fonctionnels impératifs.
L'utilisation d'effets secondaires pour les E / S est probablement correcte. Souvent, les E / S (autres que la journalisation) ne sont effectuées qu'à la limite extérieure de votre système. Aucune communication externe ne se produit dans votre logique métier. Il est alors possible d'écrire le cœur de votre logiciel dans un style pur, tout en effectuant des E / S impures dans une coque externe. Cela signifie également que le noyau peut être apatride.
L'apatridie présente un certain nombre d'avantages pratiques, tels qu'une rationalité et une évolutivité accrues. Ceci est très populaire pour les backends d'applications Web. Tout état est conservé à l'extérieur, dans une base de données partagée. Cela facilite l'équilibrage de charge: je n'ai pas à coller les sessions à un serveur spécifique. Et si j'ai besoin de plus de serveurs? Ajoutez-en simplement un autre, car il utilise la même base de données. Et si un serveur tombe en panne? Je peux refaire toutes les demandes en attente sur un autre serveur. Bien sûr, il y a toujours un état - dans la base de données. Mais je l'ai rendu explicite et extrait, et je pourrais utiliser une approche fonctionnelle pure en interne si je le souhaite.
Aucun langage de programmation n'élimine les effets secondaires. Je pense qu'il vaut mieux dire que les langages déclaratifs contiennent des effets secondaires alors que les langages impératifs n'en contiennent pas. Cependant, je ne suis pas sûr que ce discours sur les effets secondaires fasse la différence fondamentale entre les deux types de langues et cela ressemble vraiment à ce que vous recherchez.
Je pense que cela aide à illustrer la différence avec un exemple.
La ligne de code ci-dessus pourrait être écrite dans pratiquement n'importe quelle langue, alors comment déterminer si nous utilisons un langage impératif ou déclaratif? En quoi les propriétés de cette ligne de code sont-elles différentes dans les deux classes de langage?
Dans un langage impératif (C, Java, Javascript, etc.), cette ligne de code représente simplement une étape d'un processus. Cela ne nous apprend rien sur la nature fondamentale de ces valeurs. Il nous dit qu'au moment suivant cette ligne de code (mais avant la ligne suivante,)
a
sera égal àb
plusc
mais il ne nous dit riena
au sens large.Dans un langage déclaratif (Haskell, Scheme, Excel, etc.), cette ligne de code en dit beaucoup plus. Il établit une relation invariante entre
a
et les deux autres objets de telle sorte que ce sera toujours le cas quia
est égal àb
plusc
. Notez que j'ai inclus Excel dans la liste des langages déclaratifs car même sib
ouc
change de valeur, le fait restera toujoursa
égal à leur somme.À mon avis, ce n'est pas les effets secondaires ni l'état qui rend les deux types de langues différents. Dans un langage impératif, une ligne de code particulière ne vous dit rien sur la signification globale des variables en question. En d'autres termes,
a = b + c
cela signifie seulement que pendant un très bref instant,a
est arrivé à égaler la somme deb
etc
.Pendant ce temps, dans les langages déclaratifs, chaque ligne de code établit une vérité fondamentale qui existera pendant toute la durée de vie du programme. Dans ces langues,
a = b + c
vous indique que, quoi qu'il arrive dans une autre ligne de code, ila
sera toujours égal à la somme deb
etc
.la source