Les langages de programmation fonctionnels interdisent-ils les effets secondaires?

10

Selon Wikipedia, les langages de programmation fonctionnels , qui sont déclaratifs, interdisent les effets secondaires. La programmation déclarative en général, tente de minimiser ou d'éliminer les effets secondaires.

De plus, selon Wikipedia, un effet secondaire est lié aux changements d'état. Ainsi, les langages de programmation fonctionnels, dans ce sens, ils éliminent en fait les effets secondaires, car ils ne sauvent aucun état.

Mais, en plus, un effet secondaire a une autre définition. Effet secondaire

a une interaction observable avec ses fonctions d'appel ou le monde extérieur en plus de renvoyer une valeur. Par exemple, une fonction particulière peut modifier une variable globale ou une variable statique, modifier l'un de ses arguments, déclencher une exception, écrire des données dans un affichage ou un fichier, lire des données ou appeler d'autres fonctions à effets secondaires.

En ce sens, les langages de programmation fonctionnels permettent en fait des effets secondaires, car il existe d'innombrables exemples de fonctions affectant leur monde extérieur, appelant d'autres fonctions, levant des exceptions, écrivant dans des fichiers, etc.

Alors, enfin, les langages de programmation fonctionnels permettent-ils ou non des effets secondaires?

Ou, je ne comprends pas ce qui se qualifie comme un "effet secondaire", donc les langages impératifs les autorisent et les déclaratifs non. Selon ce qui précède et ce que j'obtiens, aucun langage n'élimine les effets secondaires, donc soit je manque quelque chose sur les effets secondaires, soit la définition de Wikipedia est incorrectement large.

codebot
la source

Réponses:

26

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 printfonction 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.

amon
la source
Merci pour la réponse détaillée. Ce que je retiens comme conclusion, c'est que les effets secondaires n'affectent pas la valeur de la fonction, en raison du raisonnement équationnel, c'est pourquoi "les langages fonctionnels ne permettent pas / minimisent les effets secondaires". Les effets intégrés dans les valeurs de fonction affectent et changent l'état qui est jamais enregistré - ou enregistré en dehors du cœur du programme. En outre, les E / S se produisent dans la limite externe de la logique métier.
codebot
3
@codebot, pas tout à fait, à mon avis. S'ils sont correctement mis en œuvre, les effets secondaires de la programmation fonctionnelle doivent se refléter dans le type de retour de la fonction. Par exemple, si une fonction peut échouer (si un fichier particulier n'existe pas ou si une connexion à la base de données ne peut pas être établie), le type de retour de la fonction doit encapsuler l'échec, plutôt que de demander à la fonction de lever une exception. Jetez un oeil à Railway-Oriented Programming pour un exemple ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Aaron M. Eshbach
"... on pourrait les appeler des langages fonctionnels impératifs.": Simon Peyton Jones a écrit "... Haskell est le meilleur langage de programmation impératif du monde.".
Giorgio
5

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.

a = b + c

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,) asera égal à bplus cmais il ne nous dit rien aau 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 aet les deux autres objets de telle sorte que ce sera toujours le cas qui aest égal à bplus c. Notez que j'ai inclus Excel dans la liste des langages déclaratifs car même si bou cchange de valeur, le fait restera toujours aé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 + ccela signifie seulement que pendant un très bref instant, aest arrivé à égaler la somme de bet c.

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 + cvous indique que, quoi qu'il arrive dans une autre ligne de code, il asera toujours égal à la somme de bet c.

Daniel T.
la source