Quelqu'un pourrait-il expliquer pourquoi les calculs impurs dans Haskell sont modélisés comme des monades?
Je veux dire que la monade est juste une interface avec 4 opérations, alors quel était le raisonnement pour modéliser les effets secondaires?
haskell
functional-programming
monads
Bodacydo
la source
la source
return
et(>>=)
.x >> y
est identique àx >>= \\_ -> y
(c'est-à-dire qu'il ignore le résultat du premier argument). On n'en parle pasfail
.fail
est dans laMonad
classe à cause d'un accident historique; il appartient vraimentMonadPlus
. Notez que sa définition par défaut n'est pas sûre.Réponses:
Supposons qu'une fonction ait des effets secondaires. Si nous prenons tous les effets qu'elle produit comme paramètres d'entrée et de sortie, alors la fonction est pure pour le monde extérieur.
Donc, pour une fonction impure
nous ajoutons le RealWorld à la considération
puis
f
est de nouveau pur. Nous définissons un type de données paramétrétype IO a = RealWorld -> (a, RealWorld)
, nous n'avons donc pas besoin de taper RealWorld autant de fois, et pouvons simplement écrirePour le programmeur, manipuler directement un RealWorld est trop dangereux - en particulier, si un programmeur met la main sur une valeur de type RealWorld, il peut essayer de la copier , ce qui est fondamentalement impossible. (Pensez à essayer de copier tout le système de fichiers, par exemple. Où le mettrez-vous?) Par conséquent, notre définition d'E / S encapsule également les états du monde entier.
Composition des fonctions "impures"
Ces fonctions impures sont inutiles si nous ne pouvons pas les enchaîner. Considérer
Nous voulons
Comment le ferions-nous si nous pouvions accéder aux états du monde réel?
Nous voyons un modèle ici. Les fonctions sont appelées comme ceci:
Nous pourrions donc définir un opérateur
~~~
pour les lier:alors nous pourrions simplement écrire
sans toucher au monde réel.
"Impurification"
Supposons maintenant que nous voulions également mettre le contenu du fichier en majuscules. Les majuscules sont une fonction pure
Mais pour entrer dans le monde réel, il doit renvoyer un fichier
IO String
. Il est facile de lever une telle fonction:Cela peut être généralisé:
pour que
impureUpperCase = impurify . upperCase
, et nous pouvons écrire(Remarque: normalement, nous écrivons
getLine ~~~ getContents ~~~ (putStrLn . upperCase)
)Nous avons toujours travaillé avec des monades
Voyons maintenant ce que nous avons fait:
(~~~) :: IO b -> (b -> IO c) -> IO c
qui enchaîne deux fonctions impures ensembleimpurify :: a -> IO a
qui convertit une valeur pure en valeur impure.Maintenant nous faisons l'identification
(>>=) = (~~~)
etreturn = impurify
, et voyons? Nous avons une monade.Note technique
Pour s'assurer que c'est vraiment une monade, il y a encore quelques axiomes qui doivent également être vérifiés:
return a >>= f = f a
f >>= return = f
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
Laissé comme exercice.
la source
RealWorld
en ... eh bien, vous verrez.IO
, car ce dernier prend en charge l'interaction, la concurrence et le non-déterminisme. Voir ma réponse à cette question pour quelques conseils supplémentaires.IO
cette façon, maisRealWorld
ne représente pas réellement le monde réel, c'est juste un jeton pour maintenir les opérations dans l'ordre (la "magie" est queRealWorld
c'est le seul type d'unicité de GHC Haskell)IO
via une combinaison de cette représentation et de la magie du compilateur non standard (qui rappelle le célèbre virus du compilateur C de Ken Thompson ). Pour les autres types, la vérité est dans le code source avec la sémantique habituelle de Haskell.Cette question contient un malentendu généralisé. L'impureté et la Monade sont des notions indépendantes. L'impureté n'est pas modélisée par Monad. Au contraire, il existe quelques types de données, tels que
IO
, qui représentent un calcul impératif. Et pour certains de ces types, une infime fraction de leur interface correspond au modèle d'interface appelé "Monad". De plus, il n'y a pas d'explication pure / fonctionnelle / dénotative connue deIO
(et il est peu probable qu'il y en ait une, compte tenu du but de "sin bin" deIO
), bien qu'il y ait l'histoire communément racontée sur le fait d'World -> (a, World)
être le sens deIO a
. Cette histoire ne peut pas décrire avec véritéIO
, carIO
prend en charge la concurrence et le non-déterminisme. L'histoire ne fonctionne même pas pour les calculs déterministes qui permettent une interaction à mi-calcul avec le monde.Pour plus d'explications, consultez cette réponse .
Edit : En relisant la question, je ne pense pas que ma réponse soit tout à fait sur la bonne voie. Les modèles de calcul impératif s'avèrent souvent être des monades, comme le disait la question. Le demandeur pourrait ne pas vraiment supposer que la monadicité permet en aucune façon la modélisation du calcul impératif.
la source
RealWorld
est, comme le disent les documents, "profondément magique". C'est un jeton qui représente ce que fait le système d'exécution, cela ne signifie en fait rien sur le monde réel. Vous ne pouvez même pas en créer un nouveau pour faire un "fil" sans faire de supercherie supplémentaire; l'approche naïve ne ferait que créer une seule action de blocage avec beaucoup d'ambiguïté quant au moment de son exécution.Monad
en général , elles en ont vraiment.Monad
en général", je veux dire grossièrementforall m. Monad m => ...
, c'est-à-dire travailler sur une instance arbitraire. Les choses que vous pouvez faire avec une monade arbitraire sont presque exactement les mêmes choses que vous pouvez faire avecIO
: recevoir des primitives opaques (comme arguments de fonction, ou à partir de bibliothèques, respectivement), construire des no-ops avecreturn
ou transformer une valeur de manière irréversible en utilisant(>>=)
. L'essence de la programmation dans une monade arbitraire est de générer une liste d'actions irrévocables: "faire X, puis faire Y, puis ...". Cela me semble assez impératif!m
comme existentiel pourrait être plus utile. De plus, mon «interprétation» est une reformulation des lois; la liste des instructions "do X" est précisément le monoïde libre sur la structure inconnue créée via(>>=)
; et les lois de monade ne sont que des lois monoïdes sur la composition des endofoncteurs.IO
est un cas pathologique précisément parce qu'il n'offre presque rien de plus que ce minimum. Dans des cas spécifiques, les types peuvent révéler plus de structure et avoir ainsi une signification réelle; mais sinon, les propriétés essentielles d'une monade - basées sur les lois - sont aussi antithétiques à la dénotation claire que l'IO
est. Sans exporter les constructeurs, énumérer de manière exhaustive les actions primitives, ou quelque chose de similaire, la situation est sans espoir.Si je comprends bien, quelqu'un du nom d' Eugenio Moggi a d' abord remarqué qu'une construction mathématique auparavant obscure appelée «monade» pouvait être utilisée pour modéliser les effets secondaires dans les langages informatiques, et donc spécifier leur sémantique à l'aide du calcul Lambda. Lors du développement de Haskell, les calculs impurs étaient modélisés de diverses manières (voir l'article «hair shirt» de Simon Peyton Jones pour plus de détails), mais lorsque Phil Wadler a introduit les monades, il est rapidement devenu évident que c'était la réponse. Et le reste est de l'histoire.
la source
Eh bien, parce que Haskell est pur . Vous avez besoin d'un concept mathématique pour faire la distinction entre les calculs impurs et les calculs purs au niveau du type et pour modéliser les flux de programme respectivement.
Cela signifie que vous devrez vous retrouver avec un type
IO a
qui modélise un calcul impur. Ensuite, vous devez savoir comment combiner ces calculs, qui s'appliquent en séquence (>>=
) et soulevez une valeur (return
) sont les plus évidents et les plus élémentaires.Avec ces deux, vous avez déjà défini une monade (sans même y penser);)
De plus, les monades fournissent des abstractions très générales et puissantes , de sorte que de nombreux types de flux de contrôle peuvent être généralisés de manière pratique dans des fonctions monadiques telles que
sequence
,liftM
ou une syntaxe spéciale, ce qui fait que l'impureté n'est pas un cas si particulier.Voir les monades dans la programmation fonctionnelle et le typage d'unicité (la seule alternative que je connaisse) pour plus d'informations.
la source
Comme vous le dites,
Monad
c'est une structure très simple. La moitié de la réponse est:Monad
est la structure la plus simple que nous pourrions éventuellement donner aux fonctions à effet secondaire et pouvoir les utiliser. AvecMonad
nous pouvons faire deux choses: nous pouvons traiter une valeur pure comme une valeur à effet secondaire (return
), et nous pouvons appliquer une fonction à effet secondaire à une valeur à effet secondaire pour obtenir une nouvelle valeur à effet secondaire (>>=
). Perdre la capacité de faire l'une ou l'autre de ces choses serait paralysant, donc notre type d'effet secondaire doit être "au moins"Monad
, et il s'avère queMonad
c'est suffisant pour implémenter tout ce dont nous avons besoin jusqu'à présent.L'autre moitié est: quelle est la structure la plus détaillée que nous pourrions donner aux «effets secondaires possibles»? On peut certainement penser à l'espace de tous les effets secondaires possibles comme un ensemble (la seule opération qui nécessite est l'appartenance). Nous pouvons combiner deux effets secondaires en les faisant l'un après l'autre, et cela donnera lieu à un effet secondaire différent (ou peut-être le même - si le premier était "éteindre l'ordinateur" et le second était "écrire un fichier", alors le résultat de la composition de ceux-ci est juste «ordinateur d'arrêt»).
Ok, alors que dire de cette opération? C'est associatif; c'est-à-dire que si nous combinons trois effets secondaires, l'ordre dans lequel nous effectuons la combinaison n'a pas d'importance. Si nous le faisons (écrire un fichier puis lire la socket) puis arrêter l'ordinateur, c'est la même chose que d'écrire un fichier alors (lire socket puis arrêter ordinateur). Mais ce n'est pas commutatif: ("write file" puis "delete file") est un effet secondaire différent de ("delete file" puis "write file"). Et nous avons une identité: l'effet secondaire spécial "pas d'effets secondaires" fonctionne ("pas d'effets secondaires" puis "supprimer le fichier" est le même effet secondaire que simplement "supprimer le fichier"). A ce stade, tout mathématicien pense "Groupe!" Mais les groupes ont des inverses, et il n'y a aucun moyen d'inverser un effet secondaire en général; "supprimer le fichier" est irréversible. Donc, la structure que nous avons laissée est celle d'un monoïde, ce qui signifie que nos fonctions à effet secondaire devraient être des monades.
Existe-t-il une structure plus complexe? Sûr! Nous pourrions diviser les effets secondaires possibles en effets basés sur le système de fichiers, effets basés sur le réseau et plus, et nous pourrions proposer des règles de composition plus élaborées qui préservent ces détails. Mais encore une fois, cela revient à:
Monad
est très simple et pourtant assez puissant pour exprimer la plupart des propriétés qui nous intéressent. (En particulier, l'associativité et les autres axiomes nous permettent de tester notre application par petits morceaux, avec la certitude que les effets secondaires de l'application combinée seront les mêmes que la combinaison des effets secondaires des pièces).la source
C'est en fait une manière assez claire de penser les E / S de manière fonctionnelle.
Dans la plupart des langages de programmation, vous effectuez des opérations d'entrée / sortie. Dans Haskell, imaginez écrire du code non pas pour effectuer les opérations, mais pour générer une liste des opérations que vous aimeriez faire.
Les monades sont juste une jolie syntaxe pour exactement cela.
Si vous voulez savoir pourquoi les monades par rapport à autre chose, je suppose que la réponse est que c'est le meilleur moyen fonctionnel de représenter les E / S auquel les gens pourraient penser lorsqu'ils fabriquaient Haskell.
la source
AFAIK, la raison est de pouvoir inclure des contrôles d'effets secondaires dans le système de type. Si vous voulez en savoir plus, écoutez ces épisodes de SE-Radio : Episode 108: Simon Peyton Jones sur la programmation fonctionnelle et Haskell Episode 72: Erik Meijer sur LINQ
la source
Ci-dessus, il y a de très bonnes réponses détaillées avec un fond théorique. Mais je veux donner mon avis sur la monade IO. Je ne suis pas programmeur de haskell expérimenté, donc peut-être est-ce assez naïf ou même faux. Mais je m'a aidé à gérer la monade IO dans une certaine mesure (notez que cela ne concerne pas d'autres monades).
Tout d'abord, je tiens à dire que cet exemple avec "le monde réel" n'est pas trop clair pour moi car nous ne pouvons pas accéder à ses états précédents (du monde réel). Peut-être que cela ne concerne pas du tout les calculs monades, mais cela est souhaité dans le sens de la transparence référentielle, qui est généralement présente dans le code haskell.
Nous voulons donc que notre langage (haskell) soit pur. Mais nous avons besoin d'opérations d'entrée / sortie car sans elles, notre programme ne peut pas être utile. Et ces opérations ne peuvent pas être pures de par leur nature. Donc, la seule façon de gérer cela, nous devons séparer les opérations impures du reste du code.
Ici la monade arrive. En fait, je ne suis pas sûr qu'il ne puisse exister d'autre construction avec des propriétés nécessaires similaires, mais le fait est que la monade a ces propriétés, donc elle peut être utilisée (et elle est utilisée avec succès). La principale propriété est que nous ne pouvons pas y échapper. L'interface Monad n'a pas d'opérations pour se débarrasser de la monade autour de notre valeur. D'autres monades (non IO) fournissent de telles opérations et permettent la correspondance de modèle (par exemple, Maybe), mais ces opérations ne sont pas dans l'interface monade. Une autre propriété requise est la capacité d'enchaîner les opérations.
Si nous réfléchissons à ce dont nous avons besoin en termes de système de types, nous arrivons au fait que nous avons besoin de type avec constructeur, qui peut être enroulé autour de n'importe quelle vallée. Le constructeur doit être privé, car nous interdisons de s'en échapper (c'est-à-dire la correspondance de motifs). Mais nous avons besoin d'une fonction pour mettre de la valeur dans ce constructeur (ici le retour vient à l'esprit). Et nous avons besoin du moyen d'enchaîner les opérations. Si nous y réfléchissons pendant un certain temps, nous arriverons au fait que l'opération de chaînage doit avoir le type >> = a. Donc, nous arrivons à quelque chose de très similaire à la monade. Je pense que si nous analysons maintenant des situations contradictoires possibles avec cette construction, nous arriverons à des axiomes monades.
Notez que cette construction développée n'a rien de commun avec l'impureté. Il n'a que des propriétés, que nous souhaitons avoir pour pouvoir faire face à des opérations impures, à savoir le non-échappement, le chaînage et un moyen d'entrer.
Maintenant, un certain ensemble d'opérations impures est prédéfini par la langue dans cette monade IO sélectionnée. Nous pouvons combiner ces opérations pour créer de nouvelles opérations impures. Et toutes ces opérations devront avoir IO dans leur type. Notez cependant que la présence d'E / S dans le type de certaines fonctions ne rend pas cette fonction impure. Mais si je comprends bien, c'est une mauvaise idée d'écrire des fonctions pures avec IO dans leur type, car c'était initialement notre idée de séparer les fonctions pures et impures.
Enfin, je veux dire que la monade ne transforme pas les opérations impures en opérations pures. Cela permet seulement de les séparer efficacement. (Je répète, que ce n'est que ma compréhension)
la source