web-dev-qa-db-fra.com

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

Selon Wikipedia, langages de programmation fonctionnels , qui sont déclaratifs, ils interdisent les effets secondaires. 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 le permettent et le déclaratif 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.

10
codebot

La programmation fonctionnelle comprend de nombreuses techniques différentes. Certaines techniques sont bien avec des effets secondaires. Mais un aspect important est 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 est de cesser d'utiliser les effets side et de coder 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 mutation d'état. Les langages C/C++/Rust ont un système de type qui peut interdire la mutation de certaines valeurs.

Dans un langage impératif, une fonction print("foo") imprimera quelque chose et ne renverra rien. Dans un langage fonctionnel pur comme Haskell, une fonction print prend également un objet représentant l'état du monde extérieur et retourne 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 seulement 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 autorisent toujours des effets secondaires - 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 un shell 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.

26
amon

Pas de langage de programmation élimine les effets secondaires. Je pense qu'il vaut mieux dire que les langages déclaratifs contiennent les effets secondaires alors que les langages impératifs ne le font 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,) a sera égal à b plus c mais il ne nous dit rien a 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 toujours le cas où a soit égal à b plus c. Notez que j'ai inclus Excel dans la liste des langages déclaratifs car même si b ou c change de valeur, il n'en restera pas moins que a sera égal à leur somme.

À mon avis ce, et non les effets secondaires ou l'état, est ce 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 signifie seulement que pendant un très bref instant, a s'est avéré égal à la somme de b et 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 + c vous indique que, quoi qu'il arrive dans une autre ligne de code, a sera toujours égal à la somme de b et c.

5
Daniel T.