Je pense que les effets secondaires sont un phénomène naturel. Mais cela ressemble à un tabou dans les langages fonctionnels. Quelles sont les raisons?
Ma question est spécifique au style de programmation fonctionnelle. Pas tous les langages / paradigmes de programmation.
Réponses:
Écrire vos fonctions / méthodes sans effets secondaires - de sorte que ce sont des fonctions pures - facilite la réflexion sur l'exactitude de votre programme.
Cela facilite également la composition de ces fonctions pour créer un nouveau comportement.
Cela permet également certaines optimisations, où le compilateur peut par exemple mémoriser les résultats de fonctions ou utiliser Common Elimination Expression.
Edit: à la demande de Benjol: Comme une grande partie de votre état est stockée dans la pile (flux de données, pas de flux de contrôle, comme Jonas l’a appelé ici ), vous pouvez paralléliser ou réorganiser l’exécution de parties de votre calcul indépendantes de L'une et l'autre. Vous pouvez facilement trouver ces parties indépendantes, car une partie ne fournit pas d'entrées à l'autre.
Dans les environnements avec des débogueurs qui vous permettent de restaurer la pile et de reprendre l'informatique (comme Smalltalk), des fonctions pures vous permettent de voir très facilement comment une valeur change, car les états précédents sont disponibles pour l'inspection. Dans un calcul comportant beaucoup de mutations, à moins d'ajouter explicitement des actions à / annuler à votre structure ou à votre algorithme, vous ne pouvez pas voir l'historique des calculs. (Cela renvoie au premier paragraphe: écrire des fonctions pures facilite l' inspection de l'exactitude de votre programme.)
la source
Extrait d'un article sur la programmation fonctionnelle :
la source
Vous vous trompez, la programmation fonctionnelle encourage la limitation des effets secondaires pour rendre les programmes faciles à comprendre et à optimiser. Même Haskell vous permet d'écrire dans des fichiers.
En gros, ce que je dis, c'est que les programmeurs fonctionnels ne pensent pas que les effets secondaires sont néfastes, ils pensent simplement qu'il est bon de limiter l'utilisation de ces effets secondaires. Je sais que cela peut sembler être une distinction si simple, mais cela fait toute la différence.
la source
readFile
c'est définir une séquence d'actions. Cette séquence est fonctionnellement pure et ressemble un peu à un arbre abstrait décrivant QUE faire. les effets secondaires réels sont alors effectués par le moteur d'exécution.Quelques notes:
Les fonctions sans effets secondaires peuvent être exécutées en parallèle, alors que les fonctions avec effets secondaires nécessitent généralement une sorte de synchronisation.
Les fonctions sans effets secondaires permettent une optimisation plus agressive (par exemple, en utilisant de manière transparente un cache de résultats), car tant que nous obtenons le bon résultat, le fait que la fonction soit réellement exécutée n'a pas d'importance
la source
deterministic
clause pour les fonctions sans effets secondaires, afin qu'elles ne soient pas exécutées plus souvent que nécessaire.deterministic
clause est juste un mot-clé qui indique au compilateur qu'il s'agit d'une fonction déterministe, comparable à la façon dont lefinal
mot - clé en Java indique au compilateur que la variable ne peut pas être modifiée.Je travaille principalement dans le code fonctionnel maintenant, et de ce point de vue, cela semble aveuglant. Les effets secondaires créent une énorme charge mentale sur les programmeurs qui essaient de lire et de comprendre le code. Vous ne remarquerez pas ce fardeau tant que vous n'en serez plus libéré pendant un moment, puis vous devrez soudainement lire à nouveau le code avec des effets secondaires.
Considérez cet exemple simple:
Dans un langage fonctionnel, je sais qu'il en
foo
reste 42. Je n'ai même pas à regarder le code entre les deux, encore moins le comprendre, ni à regarder la mise en œuvre des fonctions qu'il appelle.Tout ce qui concerne la simultanéité, la parallélisation et l'optimisation est une bonne chose, mais c'est ce que les informaticiens ont mis dans la brochure. Ne pas avoir à se demander qui mue votre variable et quand est ce que j'apprécie vraiment dans la pratique quotidienne.
la source
Peu ou pas de langues rendent impossible de provoquer des effets secondaires. Les langues totalement libres d’effets secondaires seraient extrêmement difficiles (presque impossibles) à utiliser, sauf dans des cas très limités.
Parce qu’ils rendent beaucoup plus difficile la tâche de raisonner sur ce que fait exactement un programme et de prouver qu’il fait ce que vous attendez de lui.
À un niveau très élevé, imaginez que vous testiez un site Web à trois niveaux avec uniquement des tests en boîte noire. Bien sûr, c'est faisable, selon l'échelle. Mais il y a certainement beaucoup de doublons. Et s'il est un bug (qui est lié à un effet secondaire), alors vous pourriez potentiellement casser l'ensemble du système pour des tests supplémentaires jusqu'à ce que le bogue est diagnostiqué et fixe, et le correctif est déployé dans l'environnement de test.
Avantages
Maintenant, réduisez-le. Si vous étiez assez bon pour écrire du code libre à effets secondaires, à quel point iriez-vous plus vite pour raisonner ce que certains codes existants ont fait? Combien de temps pourriez-vous écrire des tests unitaires? À quel point penseriez-vous que le code sans effets secondaires était garanti sans bugs et que les utilisateurs pouvaient limiter leur exposition aux bugs qu'il avait ?
Si le code n'a pas d'effets secondaires, le compilateur peut également effectuer des optimisations supplémentaires. Il peut être beaucoup plus facile de mettre en œuvre ces optimisations. Il peut être beaucoup plus facile même de conceptualiser une optimisation pour un code sans effets secondaires, ce qui signifie que le fournisseur de votre compilateur peut implémenter des optimisations difficiles à impossibles dans un code avec des effets secondaires.
La simultanéité est également considérablement plus simple à mettre en œuvre, à générer automatiquement et à optimiser lorsque le code n'a pas d'effets secondaires. En effet, toutes les pièces peuvent être évaluées en toute sécurité dans n'importe quel ordre. Permettre aux programmeurs d'écrire du code hautement concurrent est largement considéré comme le prochain défi de taille auquel l'informatique doit s'attaquer, et comme l'une des dernières zones de couverture contre la loi de Moore .
la source
Les effets secondaires sont comme des "fuites" dans votre code qui devront être traités plus tard, soit par vous-même, soit par un collègue peu méfiant.
Les langages fonctionnels évitent les variables d'état et les données modifiables, ce qui permet de rendre le code moins dépendant du contexte et plus modulaire. La modularité assure que le travail d'un développeur n'affectera pas / ne nuira pas au travail d'un autre.
La vitesse de développement proportionnelle à la taille de l'équipe est aujourd'hui un "Saint Graal" du développement logiciel. Lorsque vous travaillez avec d'autres programmeurs, peu de choses sont aussi importantes que la modularité. Même le plus simple des effets secondaires logiques rend la collaboration extrêmement difficile.
la source
Eh bien, à mon humble avis, c'est assez hypocrite. Personne n'aime les effets secondaires, mais tout le monde en a besoin.
Ce qui est tellement dangereux avec les effets secondaires, c’est que si vous appelez une fonction, cela aura peut-être un effet non seulement sur le comportement de la fonction lors de son appel suivant, mais aussi sur d’autres fonctions. Ainsi, les effets secondaires introduisent un comportement imprévisible et des dépendances non triviales.
Les paradigmes de programmation tels que OO et fonctionnel répondent tous deux à ce problème. OO réduit le problème en imposant une séparation des préoccupations. Cela signifie que l'état de l'application, qui comprend un grand nombre de données mutables, est encapsulé dans des objets, chacun étant responsable de la gestion de son propre état uniquement. De cette façon, le risque de dépendance est réduit et les problèmes sont beaucoup plus isolés et plus faciles à suivre.
La programmation fonctionnelle adopte une approche beaucoup plus radicale, où l'état de l'application est simplement immuable du point de vue du programmeur. C'est une bonne idée, mais rend le langage inutile par lui-même. Pourquoi? Parce que TOUT fonctionnement I / O a des effets secondaires. Dès que vous lisez un flux d'entrée, l'état de votre application est susceptible de changer car, lors de la prochaine invocation de la même fonction, le résultat sera probablement différent. Vous êtes peut-être en train de lire différentes données ou, éventuellement, l’opération peut échouer. La même chose est vraie pour la sortie. Même la sortie est une opération avec des effets secondaires. Vous ne vous en rendez pas souvent compte, mais imaginez que vous n’avez que 20 Ko pour votre sortie et que si vous restez plus productif, votre application se bloque parce que vous n’avez plus assez d’espace disque.
Alors oui, les effets secondaires sont méchants et dangereux du point de vue d'un programmeur. La plupart des bugs proviennent de la façon dont certaines parties de l'état de l'application sont imbriquées de manière presque obscure, par le biais d'effets secondaires inconsidérés et souvent inutiles. Du point de vue de l'utilisateur, les effets secondaires sont le point essentiel de l'utilisation d'un ordinateur. Ils ne se soucient pas de ce qui se passe à l'intérieur ou de la manière dont cela est organisé. Ils font quelque chose et s'attendent à ce que l'ordinateur change en conséquence.
la source
Tout effet secondaire introduit des paramètres d’entrée / sortie supplémentaires qui doivent être pris en compte lors des tests.
Cela rend la validation du code beaucoup plus complexe car l’environnement ne peut pas être limité au seul code en cours de validation, mais doit intégrer tout ou partie de l’environnement environnant (le global qui est mis à jour vit dans ce code là-bas, ce qui dépend code, qui dépend à son tour de la vie sur un serveur Java EE complet ....)
En essayant d'éviter les effets secondaires, vous limitez la quantité d'externalisme nécessaire à l'exécution du code.
la source
D'après mon expérience, une bonne conception de la programmation orientée objet oblige à utiliser des fonctions ayant des effets secondaires.
Par exemple, prenons une application de bureau d'interface utilisateur de base. J'ai peut-être un programme en cours d'exécution qui a sur son tas un graphe d'objet représentant l'état actuel du modèle de domaine de mon programme. Les messages parviennent aux objets de ce graphique (par exemple, via des appels de méthodes invoqués à partir du contrôleur de couche de l'interface utilisateur). Le graphe d'objet (modèle de domaine) sur le tas est modifié en réponse aux messages. Les observateurs du modèle sont informés de tout changement, l'interface utilisateur et éventuellement d'autres ressources sont modifiées.
Loin d’être maléfique, l’arrangement correct de ces effets secondaires de modification de tas et de modification d’écran est au cœur de la conception OO (dans ce cas, le modèle MVC).
Bien entendu, cela ne signifie pas que vos méthodes doivent avoir des effets secondaires arbitraires. Et les fonctions sans effets secondaires ont leur place dans l'amélioration de la lisibilité et parfois des performances de votre code.
la source
Le mal est un peu excessif… tout dépend du contexte d'utilisation de la langue.
Une autre considération pour ceux déjà mentionnés est que cela simplifie beaucoup la preuve de la correction d'un programme s'il n'y a pas d'effets secondaires fonctionnels.
la source
Comme les questions ci - dessus ont souligné, les langages fonctionnels ne pas-bien empêcher le code d'avoir des effets secondaires comme nous fournir des outils pour gérer les effets secondaires peuvent se produire dans un morceau de code donné et quand.
Cela s'avère avoir des conséquences très intéressantes. Premièrement, et bien évidemment, il existe de nombreuses choses que vous pouvez faire avec du code libre d’effets secondaires, qui ont déjà été décrites. Mais il y a d'autres choses que nous pouvons faire aussi, même lorsque vous travaillez avec du code qui a des effets secondaires:
la source
Dans les bases de code complexes, les interactions complexes des effets secondaires sont la chose la plus difficile à raisonner. Je ne peux parler personnellement que de la manière dont fonctionne mon cerveau. Les effets secondaires et les états persistants et les entrées en mutation, etc., me poussent à penser au «quand» et au «où» il arrive que la raison soit rationnelle, et pas seulement «ce qui» se passe dans chaque fonction.
Je ne peux pas me concentrer sur "quoi". Je ne peux pas conclure, après avoir testé de manière approfondie une fonction qui provoque des effets secondaires, qu'elle produira un air de fiabilité dans le code, car les appelants risquent de l'utiliser à mauvais escient en l'appelant au mauvais moment, à partir du mauvais thread, du mauvais ordre. Pendant ce temps, une fonction qui ne provoque aucun effet secondaire et renvoie simplement une nouvelle sortie à partir d’une entrée (sans toucher l’entrée) est quasiment impossible à utiliser de la sorte.
Mais je suis du genre pragmatique, ou du moins je l’essaye, et je ne pense pas que nous devions éliminer tous les effets secondaires au minimum pour pouvoir raisonner au sujet de l’exactitude de notre code (à tout le moins Je trouverais cela très difficile à faire dans des langues comme C). Il m'est très difficile de raisonner sur l'exactitude lorsque nous combinons des flux de contrôle complexes et des effets secondaires.
Les flux de contrôles complexes sont ceux qui sont de type graphique, souvent récursifs ou récursifs (files d'attente d'événements, par exemple, qui n'appellent pas directement des événements de manière récursive mais qui sont de nature "récursive"), peut-être que en train de traverser une structure de graphe liée réelle ou de traiter une file d'attente d'événements non homogène contenant un mélange éclectique d'événements à traiter, ce qui nous conduit à toutes sortes de parties différentes de la base de code et à toutes les différentes causes d'effets secondaires. Si vous essayez de tracer tous les endroits que vous allez finir dans le code, cela ressemblera à un graphe complexe et potentiellement avec des nœuds dans le graphe que vous n’auriez jamais imaginés se trouveraient à cet instant donné, et étant donné qu’ils sont tous provoquant des effets secondaires,
Les langages fonctionnels peuvent avoir des flux de contrôle extrêmement complexes et récursifs, mais le résultat est si facile à comprendre en termes de correction car il n’ya pas toutes sortes d’effets secondaires éclectiques dans le processus. Ce n'est que lorsque des flux de contrôle complexes rencontrent des effets secondaires éclectiques que j'ai du mal à me faire mal à la tête d'essayer de comprendre l'ensemble de ce qui se passe et de savoir si cela ira toujours dans le bon sens.
Ainsi, lorsque j’ai ces cas-là, j’ai souvent beaucoup de difficulté, voire d’impossibilité, à avoir confiance en la justesse de ce code, et encore moins à la possibilité de modifier ce code sans trébucher. Donc, la solution pour moi est de simplifier le flux de contrôle ou de minimiser / unifier les effets secondaires (en unifiant, je veux dire comme causant seulement un type d’effet secondaire à beaucoup de choses pendant une phase particulière du système, pas deux ou trois ou douzaine). J'ai besoin de l'une de ces deux choses pour permettre à mon cerveau simple de se sentir confiant quant à l'exactitude du code existant et à l'exactitude des modifications que je présente. Il est assez facile d’être confiant quant à l’exactitude du code introduisant des effets secondaires si les effets secondaires sont uniformes et simples, de même que le flux de contrôle, comme suit:
Il est assez facile de raisonner sur l'exactitude d'un tel code, mais principalement parce que les effets secondaires sont si uniformes et que le contrôle des flux est tellement simple. Mais disons que nous avions un code comme celui-ci:
Ensuite, il s’agit d’un pseudocode ridiculement surimplifié qui impliquerait généralement beaucoup plus de fonctions et de boucles imbriquées et beaucoup plus de choses à faire (mise à jour de plusieurs cartes de texture, poids des os, états de sélection, etc.), mais même le pseudo-code le rend si difficile à utiliser. raison au sujet de l’exactitude en raison de l’interaction du flux de contrôle complexe de type graphique et des effets secondaires en cours. Donc, une stratégie pour simplifier cela consiste à différer le traitement et à se concentrer sur un type d’effet secondaire à la fois:
... quelque chose à cet effet comme une itération de simplification. Cela signifie que nous parcourons les données plusieurs fois, ce qui engendre un coût de calcul indéniable, mais nous constatons souvent que nous pouvons multithreading plus facilement du code résultant, maintenant que les effets secondaires et les flux de contrôle ont pris cette nature uniforme et plus simple. De plus, chaque boucle peut être rendue plus conviviale pour le cache que de parcourir le graphe connecté et d’avoir des effets secondaires au fur et à mesure (ex: utilisez un bit parallèle défini pour marquer ce qui doit être parcouru afin que nous puissions ensuite effectuer les passes différées dans un ordre séquentiel trié utilisant des bitmasks et des FFS). Mais surtout, je trouve la deuxième version tellement plus facile à raisonner en termes de correction et de modification sans causer de bugs. Pour que'
Et après tout, nous avons besoin que des effets secondaires se produisent à un moment donné, sinon nous aurions simplement des fonctions qui produiraient des données sans nulle part où aller. Nous avons souvent besoin d'enregistrer quelque chose dans un fichier, d'afficher quelque chose sur un écran, d'envoyer les données via un socket, quelque chose de ce genre, et tout cela est un effet secondaire. Mais nous pouvons certainement réduire le nombre d’effets secondaires superflus, ainsi que le nombre d’effets secondaires se produisant lorsque les flux de contrôle sont très compliqués, et je pense qu’il serait beaucoup plus facile d’éviter les bugs si nous le faisions.
la source
Ce n'est pas mal. Mon avis, il est nécessaire de distinguer les deux types de fonctions - avec effets secondaires et sans. La fonction sans effets secondaires: - retourne toujours la même chose avec les mêmes arguments, ainsi par exemple une telle fonction sans aucun argument n'a aucun sens. - Cela signifie également que l'ordre dans lequel certaines de ces fonctions sont appelées ne joue aucun rôle - doit pouvoir s'exécuter et peut être débogué tout seul (!), Sans autre code. Et maintenant, lol, regarde ce que JUnit fait. Une fonction avec des effets secondaires: - présente en quelque sorte des "fuites", ce qui peut être automatiquement mis en évidence - elle est très importante pour le débogage et la recherche des erreurs, généralement causées par des effets secondaires. - Toute fonction avec des effets secondaires a également une "partie" d'elle-même sans effets secondaires, ce qui peut également être séparé automatiquement. Si mal sont ces effets secondaires,
la source