Je peux voir que les avantages des objets mutables par rapport aux objets immuables, comme les objets immuables, enlèvent beaucoup de problèmes difficiles à résoudre dans la programmation multithread en raison de l'état partagé et accessible en écriture. Au contraire, les objets mutables aident à gérer l'identité de l'objet plutôt que de créer une nouvelle copie à chaque fois et améliorent ainsi les performances et l'utilisation de la mémoire, en particulier pour les objets plus gros.
Une chose que j'essaie de comprendre, c'est ce qui peut mal tourner en ayant des objets mutables dans le contexte de la programmation fonctionnelle. Comme l'un des points qui m'a été dit, le résultat de l'appel de fonctions dans un ordre différent n'est pas déterministe.
Je cherche un exemple concret réel où il est très évident ce qui peut mal tourner en utilisant un objet mutable dans la programmation de fonctions. Fondamentalement, si elle est mauvaise, elle est mauvaise quel que soit l'OO ou le paradigme de programmation fonctionnelle, non?
Je crois que ma propre déclaration répond elle-même à cette question. Mais j'ai encore besoin d'un exemple pour pouvoir le ressentir plus naturellement.
OO aide à gérer la dépendance et à écrire des programmes plus faciles et maintenables à l'aide d'outils tels que l'encapsulation, le polymorphisme, etc.
La programmation fonctionnelle a également le même motif de promouvoir le code maintenable, mais en utilisant un style qui élimine le besoin d'utiliser des outils et des techniques OO - dont je crois que c'est en minimisant les effets secondaires, la fonction pure, etc.
Réponses:
Je pense que l'importance est mieux démontrée en comparant à une approche OO
par exemple, disons que nous avons un objet
Dans le paradigme OO, la méthode est attachée aux données et il est logique que ces données soient mutées par la méthode.
Dans le paradigme fonctionnel, nous définissons un résultat en termes de fonction. une commande achetée EST le résultat de la fonction d'achat appliquée à une commande. Cela implique quelques éléments dont nous devons être sûrs
Vous attendriez-vous à order.Status == "Acheté"?
Cela implique également que nos fonctions sont idempotentes. c'est à dire. les exécuter deux fois devrait produire le même résultat à chaque fois.
Si la commande était modifiée par la fonction d'achat, la commande PurchaseOrder2 échouerait.
En définissant les choses comme des résultats de fonctions, cela nous permet d'utiliser ces résultats sans réellement les calculer. Ce qui en termes de programmation est une exécution différée.
Cela peut être pratique en soi, mais une fois que nous ne savons pas quand une fonction se produira ET que cela nous convient, nous pouvons tirer parti du traitement parallèle beaucoup plus que nous ne le pouvons dans un paradigme OO.
Nous savons que l'exécution d'une fonction n'affectera pas les résultats d'une autre fonction; afin que nous puissions laisser l'ordinateur pour les exécuter dans l'ordre qu'il choisit, en utilisant autant de threads qu'il le souhaite.
Si une fonction mute son entrée, nous devons être beaucoup plus prudents à ce sujet.
la source
Order Purchase() { return new Order(Status = "Purchased") }
un champ en lecture seule. ? Encore une fois, pourquoi cette pratique est plus pertinente dans le contexte du paradigme de programmation de fonction? Les avantages que vous avez mentionnés peuvent également être vus dans la programmation OO, non?La clé pour comprendre pourquoi les objets immuables sont bénéfiques ne réside pas vraiment dans la recherche d'exemples concrets dans le code fonctionnel. Étant donné que la plupart du code fonctionnel est écrit à l'aide de langages fonctionnels et que la plupart des langages fonctionnels sont immuables par défaut, la nature même du paradigme est conçue pour éviter que ce que vous recherchez ne se produise.
La chose clé à demander est, quel est cet avantage de l'immuabilité? La réponse est qu'elle évite la complexité. Disons que nous avons deux variables,
x
ety
. Les deux commencent par la valeur de1
.y
mais double toutes les 13 secondes. Quelle sera la valeur de chacun d'eux dans 20 jours?x
sera1
. C'est facile. Cela demanderait des efforts pour trouvery
un moyen plus complexe. Quelle heure dans 20 jours? Dois-je prendre en compte l'heure d'été? La complexité dey
versusx
est bien plus que cela.Et cela se produit également en vrai code. Chaque fois que vous ajoutez une valeur de mutation au mélange, cela devient une autre valeur complexe à retenir et à calculer dans votre tête, ou sur papier, lorsque vous essayez d'écrire, de lire ou de déboguer le code. Plus il y a de complexité, plus vous avez de chances de faire une erreur et d'introduire un bug. Le code est difficile à écrire; difficile à lire; difficile à déboguer: le code est difficile à obtenir correctement.
Mais la mutabilité n'est pas mauvaise . Un programme avec une mutabilité nulle ne peut avoir aucun résultat, ce qui est assez inutile. Même si la mutabilité consiste à écrire un résultat sur un écran, un disque ou autre, il doit être présent. Ce qui est mauvais, c'est une complexité inutile. L'un des moyens les plus simples de réduire la complexité consiste à rendre les choses immuables par défaut et à les rendre mutables uniquement en cas de besoin, pour des raisons de performances ou de fonctionnement.
la source
y
doit muter; c'est une exigence. Parfois, nous devons avoir un code complexe pour répondre à des exigences complexes. Le point que j'essayais de faire est qu'il fallait éviter toute complexité inutile . Les valeurs de mutation sont intrinsèquement plus complexes que les valeurs fixes, donc - pour éviter une complexité inutile - ne modifiez les valeurs que lorsque vous le devez.Les mêmes choses qui peuvent mal tourner dans la programmation non fonctionnelle: vous pouvez obtenir des effets secondaires indésirables et inattendus , ce qui est une cause bien connue d'erreurs depuis l'invention des langages de programmation étendus.
À mon humble avis, la seule vraie différence entre la programmation fonctionnelle et non fonctionnelle est que, dans le code non fonctionnel, vous vous attendez généralement à des effets secondaires, dans la programmation fonctionnelle, vous ne le ferez pas.
Bien sûr, les effets secondaires indésirables sont une catégorie de bogues, quel que soit le paradigme. L'inverse est également vrai - les effets secondaires délibérément utilisés peuvent aider à résoudre les problèmes de performances et sont généralement nécessaires pour la plupart des programmes du monde réel en ce qui concerne les E / S et les systèmes externes - également quel que soit le paradigme.
la source
Je viens de répondre à une question StackOverflow qui illustre assez bien votre question. Le principal problème avec les structures de données mutables est que leur identité n'est valide qu'à un instant précis dans le temps, donc les gens ont tendance à entasser autant qu'ils le peuvent dans le petit point du code où ils savent que l'identité est constante. Dans cet exemple particulier, il fait beaucoup de journalisation dans une boucle for:
Lorsque vous êtes habitué à l'immuabilité, vous ne craignez pas que la structure des données change si vous attendez trop longtemps, vous pouvez donc effectuer des tâches logiquement séparées à votre guise, de manière beaucoup plus découplée:
la source
L'avantage d'utiliser des objets immuables est que si l'on reçoit une référence à un objet avec qui aura une certaine propriété lorsque le récepteur l'examine, et doit donner à un autre code une référence à un objet avec cette même propriété, on peut simplement passer le long de la référence à l'objet sans tenir compte de qui d' autre pourrait avoir reçu la référence ou ce qu'ils pourraient faire l'objet [depuis n'importe qui rien de là d' autre peut faire l'objet], ou lorsque le récepteur peut examiner l'objet [puisque tous ses les propriétés seront les mêmes quel que soit le moment où elles sont examinées].
En revanche, le code qui doit donner à quelqu'un une référence à un objet mutable qui aura une certaine propriété lorsque le récepteur l'examine (en supposant que le récepteur lui-même ne le change pas) doit également savoir que rien d'autre que le récepteur ne changera jamais cette propriété, ou bien savoir quand le récepteur va accéder à cette propriété, et savoir que rien ne va changer cette propriété jusqu'à la dernière fois que le récepteur l'examinera.
Je pense qu'il est très utile, pour la programmation en général (pas seulement la programmation fonctionnelle), de penser que les objets immuables entrent dans trois catégories:
Les objets qui ne peuvent rien permettre de les changer, même avec une référence. De tels objets et leurs références se comportent comme des valeurs et peuvent être librement partagés.
Objets qui se permettraient d'être modifiés par du code qui y fait référence, mais dont les références ne seront jamais exposées à un code qui les modifierait réellement . Ces objets encapsulent des valeurs, mais ils ne peuvent être partagés qu'avec du code auquel on peut faire confiance pour ne pas les modifier ou les exposer au code qui pourrait le faire.
Objets qui seront modifiés. Ces objets sont mieux vus comme des conteneurs et leurs références comme des identificateurs .
Un modèle utile consiste souvent à demander à un objet de créer un conteneur, de le remplir à l'aide de code auquel on peut faire confiance pour ne pas conserver de référence par la suite, puis de disposer des seules références qui existeront partout dans l'univers dans un code qui ne modifiera jamais le objet une fois qu'il est rempli. Bien que le conteneur puisse être de type mutable, il peut être raisonné sur (*) comme s'il était immuable, car rien ne le mutera jamais. Si toutes les références au conteneur sont conservées dans des types de wrapper immuables qui ne modifieront jamais son contenu, ces wrappers peuvent être transmis en toute sécurité comme si les données qu'ils contenaient étaient conservées dans des objets immuables, car les références aux wrappers peuvent être librement partagées et examinées à à tout moment.
(*) Dans le code multithread, il peut être nécessaire d'utiliser des «barrières de mémoire» pour garantir qu'avant qu'un thread ne puisse voir une référence à l'encapsuleur, les effets de toutes les actions sur le conteneur soient visibles pour ce thread, mais c'est un cas spécial mentionné ici uniquement pour être complet.
la source
Comme cela a déjà été mentionné, le problème de l'état mutable est fondamentalement une sous-classe du problème plus large des effets secondaires , où le type de retour d'une fonction ne décrit pas précisément ce que fait réellement la fonction, car dans ce cas, il fait également une mutation d'état. Ce problème a été résolu par certains nouveaux langages de recherche, tels que F * ( http://www.fstar-lang.org/tutorial/ ). Ce langage crée un système d'effet similaire au système de type, où une fonction déclare non seulement statiquement son type, mais aussi ses effets. De cette façon, les appelants de la fonction sont conscients qu'une mutation d'état peut se produire lors de l'appel de la fonction et que cet effet se propage à ses appelants.
la source