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.