Après avoir brièvement examiné Haskell récemment, quelle serait une explication brève, succincte et pratique de ce qu'est essentiellement une monade?
J'ai trouvé que la plupart des explications que j'ai rencontrées étaient assez inaccessibles et manquaient de détails pratiques.
haskell
functional-programming
monads
terminology
Peter Mortensen
la source
la source
Réponses:
Premièrement: Le terme monade est un peu vague si vous n'êtes pas mathématicien. Un autre terme est constructeur de calcul qui est un peu plus descriptif de ce à quoi ils sont réellement utiles.
Vous demandez des exemples pratiques:
Exemple 1: Comprendre la liste :
Cette expression renvoie les doubles de tous les nombres impairs compris entre 1 et 10. Très utile!
Il s'avère que ce n'est vraiment que du sucre syntaxique pour certaines opérations au sein de la monade List. La même compréhension de liste peut s'écrire:
Ou même:
Exemple 2: entrée / sortie :
Les deux exemples utilisent des monades, des constructeurs de calcul AKA. Le thème commun est que la monade enchaîne les opérations d'une manière spécifique et utile. Dans la compréhension de la liste, les opérations sont chaînées de telle sorte que si une opération renvoie une liste, les opérations suivantes sont effectuées sur chaque élément de la liste. La monade IO, quant à elle, effectue les opérations de manière séquentielle, mais transmet une "variable cachée", qui représente "l'état du monde", ce qui nous permet d'écrire du code d'E / S de manière purement fonctionnelle.
Il s'avère que le schéma des opérations de chaînage est très utile et est utilisé pour beaucoup de choses différentes dans Haskell.
Un autre exemple concerne les exceptions: à l'aide de la
Error
monade, les opérations sont chaînées de manière à être exécutées de manière séquentielle, sauf en cas d'erreur, auquel cas le reste de la chaîne est abandonné.La syntaxe de compréhension de liste et la notation do sont toutes deux du sucre syntaxique pour les opérations de chaînage à l'aide de l'
>>=
opérateur. Une monade est simplement un type qui prend en charge l'>>=
opérateur.Exemple 3: un analyseur
Il s'agit d'un analyseur très simple qui analyse soit une chaîne entre guillemets, soit un nombre:
Les opérations
char
,digit
etc. sont assez simples. Ils correspondent ou ne correspondent pas. La magie est la monade qui gère le flux de contrôle: les opérations sont effectuées de manière séquentielle jusqu'à ce qu'une correspondance échoue, auquel cas la monade revient au plus tard<|>
et essaie l'option suivante. Encore une fois, un moyen de chaîner les opérations avec une sémantique supplémentaire et utile.Exemple 4: programmation asynchrone
Les exemples ci-dessus sont en Haskell, mais il s'avère que F # prend également en charge les monades. Cet exemple est volé à Don Syme :
Cette méthode récupère une page Web. La ligne de punch est l'utilisation de
GetResponseAsync
- elle attend en fait la réponse sur un thread séparé, tandis que le thread principal revient de la fonction. Les trois dernières lignes sont exécutées sur le thread généré lorsque la réponse a été reçue.Dans la plupart des autres langues, vous devez créer explicitement une fonction distincte pour les lignes qui gèrent la réponse. La
async
monade est capable de "diviser" le bloc par elle-même et de reporter l'exécution de cette dernière moitié. (Laasync {}
syntaxe indique que le flux de contrôle dans le bloc est défini par laasync
monade.)Comment ils travaillent
Alors, comment une monade peut-elle faire toutes ces fantaisies de contrôle? Ce qui se passe réellement dans un do-block (ou une expression de calcul comme on les appelle en F #), c'est que chaque opération (essentiellement chaque ligne) est encapsulée dans une fonction anonyme distincte. Ces fonctions sont ensuite combinées à l'aide de l'
bind
opérateur (orthographié>>=
en Haskell). Étant donné que l'bind
opération combine des fonctions, elle peut les exécuter comme bon lui semble: séquentiellement, plusieurs fois, en sens inverse, en rejeter certaines, en exécuter sur un thread distinct quand cela lui semble et ainsi de suite.À titre d'exemple, voici la version étendue du code IO de l'exemple 2:
C'est plus laid, mais c'est aussi plus évident ce qui se passe réellement. L'
>>=
opérateur est l'ingrédient magique: il prend une valeur (à gauche) et la combine avec une fonction (à droite) pour produire une nouvelle valeur. Cette nouvelle valeur est ensuite prise par l'>>=
opérateur suivant et à nouveau combinée avec une fonction pour produire une nouvelle valeur.>>=
peut être considéré comme un mini-évaluateur.Notez qu'il
>>=
est surchargé pour différents types, donc chaque monade a sa propre implémentation de>>=
. (Toutes les opérations de la chaîne doivent cependant être du type de la même monade, sinon l'>>=
opérateur ne fonctionnera pas.)L'implémentation la plus simple possible de
>>=
prend juste la valeur à gauche et l'applique à la fonction à droite et retourne le résultat, mais comme dit précédemment, ce qui rend l'ensemble du modèle utile, c'est quand il y a quelque chose de plus dans l'implémentation de la monade de>>=
.Il existe une certaine intelligence supplémentaire dans la façon dont les valeurs sont transmises d'une opération à la suivante, mais cela nécessite une explication plus approfondie du système de type Haskell.
Résumer
En termes Haskell, une monade est un type paramétré qui est une instance de la classe de type Monade, qui définit
>>=
avec quelques autres opérateurs. En termes simples, une monade n'est qu'un type pour lequel l'>>=
opération est définie.En soi, ce
>>=
n'est qu'une façon encombrante de chaîner des fonctions, mais avec la présence de la do-notation qui cache la "plomberie", les opérations monadiques se révèlent être une abstraction très agréable et utile, utile à de nombreux endroits dans la langue, et utile pour créer vos propres mini-langues dans la langue.Pourquoi les monades sont-elles dures?
Pour de nombreux apprenants Haskell, les monades sont un obstacle qu'ils frappent comme un mur de briques. Ce n'est pas que les monades elles-mêmes soient complexes, mais que l'implémentation repose sur de nombreuses autres fonctionnalités avancées de Haskell comme les types paramétrés, les classes de types, etc. Le problème est que les E / S Haskell sont basées sur des monades, et les E / S sont probablement l'une des premières choses que vous voulez comprendre lors de l'apprentissage d'un nouveau langage - après tout, ce n'est pas très amusant de créer des programmes qui ne produisent aucun production. Je n'ai pas de solution immédiate à ce problème de poulet et d'œuf, à part traiter les E / S comme "la magie se produit ici" jusqu'à ce que vous ayez suffisamment d'expérience avec d'autres parties du langage. Désolé.
Excellent blog sur les monades: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
la source
Expliquer "qu'est-ce qu'une monade" revient un peu à dire "qu'est-ce qu'un nombre?" Nous utilisons tout le temps des chiffres. Mais imaginez que vous avez rencontré quelqu'un qui ne savait rien des chiffres. Comment diable pourriez-vous expliquer ce que sont les chiffres? Et comment pourriez-vous même commencer à décrire pourquoi cela pourrait être utile?
Qu'est-ce qu'une monade? La réponse courte: c'est une façon spécifique de chaîner les opérations.
Essentiellement, vous écrivez des étapes d'exécution et les liez ensemble avec la "fonction de liaison". (Dans Haskell, il est nommé
>>=
.) Vous pouvez écrire les appels à l'opérateur de liaison vous-même, ou vous pouvez utiliser du sucre de syntaxe qui oblige le compilateur à insérer ces appels de fonction pour vous. Mais de toute façon, chaque étape est séparée par un appel à cette fonction de liaison.Ainsi, la fonction de liaison est comme un point-virgule; il sépare les étapes d'un processus. Le travail de la fonction de liaison consiste à prendre la sortie de l'étape précédente et à la transmettre à l'étape suivante.
Cela ne semble pas trop dur, non? Mais il existe plusieurs types de monades. Pourquoi? Comment?
Eh bien, la fonction de liaison peut simplement prendre le résultat d'une étape et le transmettre à l'étape suivante. Mais si c'est "tout" ce que fait la monade ... ce n'est vraiment pas très utile. Et c'est important de comprendre: Chaque utile monade fait quelque chose d' autre en plus d'être juste un monade. Chaque monade utile a un "pouvoir spécial", ce qui la rend unique.
(Une monade qui ne fait rien de spécial s'appelle la "monade d'identité". Un peu comme la fonction d'identité, cela ressemble à une chose tout à fait inutile, mais il s'avère que ce n'est pas le cas ... Mais c'est une autre histoire ™.)
Fondamentalement, chaque monade a sa propre implémentation de la fonction de liaison. Et vous pouvez écrire une fonction de liaison de manière à ce qu'elle fasse des choses difficiles entre les étapes d'exécution. Par exemple:
Si chaque étape renvoie un indicateur de succès / échec, vous pouvez demander à bind d'exécuter l'étape suivante uniquement si la précédente a réussi. De cette façon, une étape qui échoue annule la séquence entière "automatiquement", sans aucun test conditionnel de votre part. (La monade de l'échec .)
En étendant cette idée, vous pouvez implémenter des "exceptions". (La monade d'erreur ou la monade d' exception .) Parce que vous les définissez vous-même plutôt que d'être une fonctionnalité de langue, vous pouvez définir leur fonctionnement. (Par exemple, vous souhaitez peut-être ignorer les deux premières exceptions et annuler uniquement lorsqu'une troisième exception est levée.)
Vous pouvez faire en sorte que chaque étape renvoie plusieurs résultats et avoir la boucle de fonction de liaison sur eux, en les alimentant à l'étape suivante pour vous. De cette façon, vous n'avez pas besoin d'écrire des boucles partout lorsque vous traitez plusieurs résultats. La fonction de liaison fait "automatiquement" tout cela pour vous. (La Liste Monade .)
En plus de passer un «résultat» d'une étape à une autre, vous pouvez également demander à la fonction de liaison de transmettre des données supplémentaires . Ces données n'apparaissent plus dans votre code source, mais vous pouvez toujours y accéder de n'importe où, sans avoir à les transmettre manuellement à chaque fonction. (The Reader Monad .)
Vous pouvez faire en sorte que les "données supplémentaires" puissent être remplacées. Cela vous permet de simuler des mises à jour destructives , sans effectuer de mises à jour destructives. (La Monade d'État et sa cousine la Monade des écrivains .)
Parce que vous ne faites que simuler des mises à jour destructives, vous pouvez faire des choses triviales qui seraient impossibles avec de vraies mises à jour destructrices. Par exemple, vous pouvez annuler la dernière mise à jour ou revenir à une version antérieure .
Vous pouvez créer une monade où les calculs peuvent être suspendus , vous pouvez donc mettre votre programme en pause, entrer et bricoler les données d'état interne, puis le reprendre.
Vous pouvez implémenter des «continuations» en tant que monade. Cela vous permet de briser l'esprit des gens!
Tout cela et bien plus est possible avec des monades. Bien sûr, tout cela est également parfaitement possible sans monades aussi. C'est juste beaucoup plus facile d' utiliser des monades.
la source
En fait, contrairement à la compréhension commune des Monades, elles n'ont rien à voir avec l'État. Les monades sont simplement un moyen d'envelopper des choses et de fournir des méthodes pour effectuer des opérations sur les éléments enveloppés sans les déballer.
Par exemple, vous pouvez créer un type pour en encapsuler un autre, dans Haskell:
Pour envelopper des choses que nous définissons
Pour effectuer des opérations sans déballer, disons que vous avez une fonction
f :: a -> b
, vous pouvez le faire pour lever cette fonction et agir sur les valeurs encapsulées:C'est à peu près tout ce qu'il faut comprendre. Cependant, il s'avère qu'il existe une fonction plus générale pour effectuer ce levage , qui est
bind
:bind
peut faire un peu plusfmap
, mais pas l'inverse. En fait,fmap
ne peut être défini qu'en termes debind
etreturn
. Ainsi, lors de la définition d' une monade .. vous donner son type (ici , il a étéWrapped a
), puis dire à quel point sonreturn
et lebind
travail des opérations.Ce qui est cool, c'est que cela s'avère être un modèle si général qu'il apparaît partout, encapsuler l'état de manière pure n'est que l'un d'entre eux.
Pour un bon article sur la façon dont les monades peuvent être utilisées pour introduire des dépendances fonctionnelles et ainsi contrôler l'ordre d'évaluation, comme il est utilisé dans la monade IO de Haskell, consultez IO Inside .
Quant à comprendre les monades, ne vous en faites pas trop. Lisez à leur sujet ce que vous trouvez intéressant et ne vous inquiétez pas si vous ne comprenez pas tout de suite. Ensuite, il suffit de plonger dans une langue comme Haskell. Les monades sont l'une de ces choses où la compréhension pénètre dans votre cerveau par la pratique, un jour vous réalisez tout à coup que vous les comprenez.
la source
Mais, vous auriez pu inventer des Monades!
la source
Une monade est un type de données qui a deux opérations:
>>=
(akabind
) etreturn
(akaunit
).return
prend une valeur arbitraire et crée avec elle une instance de la monade.>>=
prend une instance de la monade et mappe une fonction dessus. (Vous pouvez déjà voir qu'une monade est un type de données étrange, car dans la plupart des langages de programmation, vous ne pouvez pas écrire une fonction qui prend une valeur arbitraire et en crée un type. Les monades utilisent une sorte de polymorphisme paramétrique .)En notation Haskell, l'interface monade est écrite
Ces opérations sont censées obéir à certaines "lois", mais ce n'est pas terriblement important: les "lois" codifient simplement la façon dont les implémentations sensées des opérations devraient se comporter (en gros, cela
>>=
etreturn
devraient convenir de la façon dont les valeurs sont transformées en instances monades et c'est>>=
associatif).Les monades ne concernent pas uniquement l'état et les E / S: elles résument un modèle de calcul commun qui inclut le travail avec l'état, les E / S, les exceptions et le non-déterminisme. Les monades les plus simples à comprendre sont probablement les listes et les types d'options:
où
[]
et:
sont les constructeurs de liste,++
est l'opérateur de concaténation, etJust
etNothing
sont lesMaybe
constructeurs. Ces deux monades encapsulent des modèles de calcul communs et utiles sur leurs types de données respectifs (notez que ni l'un ni l'autre n'a rien à voir avec les effets secondaires ou les E / S).Vous devez vraiment vous amuser à écrire du code Haskell non trivial pour apprécier ce que sont les monades et pourquoi elles sont utiles.
la source
Vous devez d'abord comprendre ce qu'est un foncteur. Avant cela, comprenez les fonctions d'ordre supérieur.
Une fonction d'ordre supérieur est simplement une fonction qui prend une fonction comme argument.
Un foncteur est n'importe quelle construction de type
T
pour laquelle il existe une fonction d'ordre supérieur, appelez-lamap
, qui transforme une fonction de typea -> b
(étant donné deux types quelconquesa
etb
) en une fonctionT a -> T b
. Cettemap
fonction doit également obéir aux lois de l'identité et de la composition de sorte que les expressions suivantes renvoient true pour tousp
etq
(notation Haskell):Par exemple, un constructeur de type appelé
List
est un foncteur s'il est équipé d'une fonction de type(a -> b) -> List a -> List b
qui obéit aux lois ci-dessus. La seule mise en œuvre pratique est évidente. LaList a -> List b
fonction résultante parcourt la liste donnée, appelle la(a -> b)
fonction pour chaque élément et renvoie la liste des résultats.Une monade est essentiellement juste un foncteur
T
avec deux méthodes supplémentaires,join
, de typeT (T a) -> T a
, etunit
(parfois appeléreturn
,fork
oupure
) de typea -> T a
. Pour les listes en Haskell:Pourquoi est-ce utile? Parce que vous pourriez, par exemple,
map
sur une liste avec une fonction qui renvoie une liste.Join
prend la liste de listes résultante et les concatène.List
est une monade parce que c'est possible.Vous pouvez écrire une fonction qui fait
map
, alorsjoin
. Cette fonction est appeléebind
, ouflatMap
, ou(>>=)
, ou(=<<)
. C'est normalement ainsi qu'une instance de monade est donnée dans Haskell.Une monade doit satisfaire à certaines lois, à savoir celle qui
join
doit être associative. Cela signifie que si vous avez une valeurx
de type,[[[a]]]
ellejoin (join x)
doit être égalejoin (map join x)
. Etpure
doit être une identité pourjoin
çajoin (pure x) == x
.la source
[Avertissement: J'essaie toujours de grogner complètement les monades. Ce qui suit est exactement ce que j'ai compris jusqu'à présent. Si c'est faux, j'espère que quelqu'un de bien informé m'appellera sur le tapis.]
Arnar a écrit:
C'est exactement ça. L'idée va comme ceci:
Vous prenez une sorte de valeur et l'enveloppez avec quelques informations supplémentaires. Tout comme la valeur est d'un certain type (par exemple un entier ou une chaîne), les informations supplémentaires sont d'un certain type.
Par exemple, cette information supplémentaire peut être un
Maybe
ou unIO
.Ensuite, vous avez certains opérateurs qui vous permettent d'opérer sur les données encapsulées tout en transportant ces informations supplémentaires. Ces opérateurs utilisent les informations supplémentaires pour décider comment modifier le comportement de l'opération sur la valeur encapsulée.
Par exemple, un
Maybe Int
peut être unJust Int
ouNothing
. Maintenant, si vous ajoutez unMaybe Int
à unMaybe Int
, l'opérateur vérifiera pour voir s'ils sont tous les deux à l'Just Int
intérieur, et si c'est le cas, déballera lesInt
s, leur passera l'opérateur d'addition, re-enveloppera le résultatInt
dans un nouveauJust Int
(qui est valideMaybe Int
), et donc retourner aMaybe Int
. Mais si l'un d'eux était unNothing
intérieur, cet opérateur reviendra immédiatementNothing
, ce qui est encore une fois valideMaybe Int
. De cette façon, vous pouvez prétendre que vosMaybe Int
chiffres ne sont que des nombres normaux et effectuer des calculs réguliers sur eux. Si vous deviez obtenir unNothing
, vos équations produiront toujours le bon résultat - sans que vous ayez à vérifier les déchetsNothing
partout .Mais l'exemple est juste ce qui se passe pour
Maybe
. Si l'information supplémentaire était unIO
, alors cet opérateur spécial défini pourIO
s serait appelé à la place, et il pourrait faire quelque chose de totalement différent avant d'effectuer l'ajout. (OK, ajouter deuxIO Int
s ensemble est probablement absurde - je ne suis pas encore sûr.) (De plus, si vous avez prêté attention à l'Maybe
exemple, vous avez remarqué que «encapsuler une valeur avec des trucs supplémentaires» n'est pas toujours correct. Mais c'est difficile être exact, correct et précis sans être impénétrable.)Fondamentalement, «monade» signifie à peu près «modèle» . Mais au lieu d'un livre plein de Patterns expliqués de manière informelle et nommés spécifiquement, vous avez maintenant une construction de langage - syntaxe et tout - qui vous permet de déclarer de nouveaux patterns en tant que choses dans votre programme . (L'imprécision ici est que tous les modèles doivent suivre une forme particulière, donc une monade n'est pas aussi générique qu'un modèle. Mais je pense que c'est le terme le plus proche que la plupart des gens connaissent et comprennent.)
Et c'est pourquoi les gens trouvent les monades si déroutantes: parce qu'elles sont un concept générique. Demander ce qui fait quelque chose d'une monade est tout aussi vague que demander ce qui fait quelque chose un motif.
Mais pensez aux implications d'avoir un support syntaxique dans la langue pour l'idée d'un modèle: au lieu d'avoir à lire le livre Gang of Four et à mémoriser la construction d'un modèle particulier, vous écrivez simplement du code qui implémente ce modèle dans un agnostique, manière générique une fois et puis vous avez terminé! Vous pouvez ensuite réutiliser ce modèle, comme Visitor ou Strategy ou Façade ou autre, simplement en décorant les opérations dans votre code avec lui, sans avoir à le réimplémenter encore et encore!
C'est pourquoi les gens qui comprennent les monades les trouvent si utiles : ce n'est pas un concept de tour d'ivoire que les snobs intellectuels se targuent de comprendre (OK, cela aussi bien sûr, teehee), mais rend en fait le code plus simple.
la source
M (M a) -> M a
. Le fait que vous puissiez transformer cela en un typeM a -> (a -> M b) -> M b
est ce qui les rend utiles.Après beaucoup d'efforts, je pense avoir enfin compris la monade. Après avoir relu ma propre longue critique de la réponse majoritairement votée, je proposerai cette explication.
Il y a trois questions auxquelles il faut répondre pour comprendre les monades:
Comme je l'ai noté dans mes commentaires originaux, trop d'explications monades se retrouvent dans la question numéro 3, sans et avant de couvrir vraiment de manière adéquate la question 2 ou la question 1.
Pourquoi avez-vous besoin d'une monade?
Les langages fonctionnels purs comme Haskell sont différents des langages impératifs comme C ou Java en ce sens qu'un programme fonctionnel pur n'est pas nécessairement exécuté dans un ordre spécifique, une étape à la fois. Un programme Haskell s'apparente davantage à une fonction mathématique, dans laquelle vous pouvez résoudre l '"équation" dans n'importe quel nombre d'ordres potentiels. Cela confère un certain nombre d'avantages, parmi lesquels il élimine la possibilité de certains types de bugs, en particulier ceux liés à des choses comme "état".
Cependant, certains problèmes ne sont pas aussi simples à résoudre avec ce style de programmation. Certaines choses, comme la programmation de la console et les E / S de fichiers, nécessitent que les choses se produisent dans un ordre particulier ou doivent conserver leur état. Une façon de résoudre ce problème consiste à créer une sorte d'objet qui représente l'état d'un calcul et une série de fonctions qui prennent un objet d'état en entrée et renvoient un nouvel objet d'état modifié.
Créons donc une valeur "d'état" hypothétique, qui représente l'état d'un écran de console. la façon exacte dont cette valeur est construite n'est pas importante, mais disons que c'est un tableau de caractères ascii de longueur d'octet qui représente ce qui est actuellement visible à l'écran, et un tableau qui représente la dernière ligne d'entrée entrée par l'utilisateur, en pseudocode. Nous avons défini certaines fonctions qui prennent l'état de la console, le modifient et renvoient un nouvel état de console.
Donc, pour faire de la programmation sur console, mais d'une manière purement fonctionnelle, vous devrez imbriquer beaucoup d'appels de fonction les uns dans les autres.
La programmation de cette manière conserve le style fonctionnel "pur", tout en forçant les modifications de la console à se produire dans un ordre particulier. Mais, nous voudrons probablement faire plus que quelques opérations à la fois comme dans l'exemple ci-dessus. Les fonctions d'imbrication de cette manière commenceront à devenir disgracieuses. Ce que nous voulons, c'est du code qui fait essentiellement la même chose que ci-dessus, mais qui s'écrit un peu plus comme ceci:
Ce serait en effet un moyen plus pratique de l'écrire. Comment faisons-nous cela cependant?
Qu'est-ce qu'une monade?
Une fois que vous avez un type (tel que
consolestate
) que vous définissez avec un tas de fonctions conçues spécifiquement pour fonctionner sur ce type, vous pouvez transformer l'ensemble complet de ces choses en une "monade" en définissant un opérateur comme:
(bind) qui automatiquement alimente les valeurs de retour à sa gauche, en paramètres de fonction à sa droite, et unlift
opérateur qui transforme les fonctions normales, en fonctions qui fonctionnent avec ce type spécifique d'opérateur de liaison.Comment une monade est-elle implémentée?
Voir d'autres réponses, qui semblent assez libres pour entrer dans les détails.
la source
Après avoir répondu à cette question il y a quelques années, je pense pouvoir améliorer et simplifier cette réponse avec ...
Une monade est une technique de composition de fonction qui externalise le traitement de certains scénarios d'entrée à l'aide d'une fonction de composition
bind
, pour prétraiter l'entrée pendant la composition.Dans une composition normale, la fonction,,
compose (>>)
est utilisée pour appliquer la fonction composée au résultat de son prédécesseur en séquence. Surtout, la fonction en cours de composition est requise pour gérer tous les scénarios de son entrée.(x -> y) >> (y -> z)
Cette conception peut être améliorée en restructurant l'entrée afin que les états pertinents soient plus facilement interrogés. Ainsi, au lieu de simplement
y
la valeur peut devenirMb
telle que, par exemple,(is_OK, b)
si elle esty
incluse une notion de validité.Par exemple, lorsque l'entrée est que peut - être un nombre, au lieu de retourner une chaîne qui peut contenir contenir docilement un certain nombre ou non, vous pourriez restructurer le type en
bool
indiquant la présence d'un numéro valide et un numéro tuple tel quebool * float
. Les fonctions composées n'auraient plus besoin d'analyser une chaîne d'entrée pour déterminer si un nombre existe, mais pourraient simplement inspecter labool
partie d'un tuple.(Ma -> Mb) >> (Mb -> Mc)
Ici encore, la composition se produit naturellement avec
compose
et chaque fonction doit donc gérer individuellement tous les scénarios de son entrée, bien que cela soit désormais beaucoup plus facile.Cependant, que se passerait-il si nous pouvions externaliser l'effort d'interrogatoire pour les moments où la gestion d'un scénario est routinière. Par exemple, si notre programme ne fait rien lorsque l'entrée est pas OK comme quand
is_OK
estfalse
. Si cela était fait, les fonctions composées n'auraient pas besoin de gérer ce scénario elles-mêmes, simplifiant considérablement leur code et effectuant un autre niveau de réutilisation.Pour réaliser cette externalisation, nous pourrions utiliser une fonction
bind (>>=)
,, pour effectuer lacomposition
place decompose
. En tant que tel, au lieu de simplement transférer des valeurs de la sortie d'une fonction à l'entrée d'une autre,Bind
il inspecterait laM
partie deMa
et déciderait si et comment appliquer la fonction composée à laa
. Bien sûr, la fonctionbind
serait définie spécifiquement pour notre particulierM
afin de pouvoir inspecter sa structure et effectuer tout type d'application que nous voulons. Néanmoins, lea
peut être n'importe quoi car ilbind
passe simplement l'a
inspecteur à la fonction composée lorsqu'il détermine que l'application est nécessaire. De plus, les fonctions composées elles-mêmes n'ont plus besoin de gérer lesM
partie de la structure d'entrée soit, en les simplifiant. Par conséquent...(a -> Mb) >>= (b -> Mc)
ou plus succinctementMb >>= (b -> Mc)
En bref, une monade extériorise et fournit ainsi un comportement standard autour du traitement de certains scénarios d'entrée une fois que l'entrée est conçue pour les exposer suffisamment. Cette conception est un
shell and content
modèle où le shell contient des données pertinentes pour l'application de la fonction composée et est interrogé par et reste uniquement disponible pour labind
fonction.Par conséquent, une monade, c'est trois choses:
M
shell pour contenir les informations pertinentes de la monade,bind
fonction implémentée pour utiliser ces informations de shell dans son application des fonctions composées aux valeurs de contenu qu'elle trouve dans le shell, eta -> Mb
produisant des résultats qui incluent des données de gestion monadiques.De manière générale, l'entrée d'une fonction est beaucoup plus restrictive que sa sortie qui peut inclure des choses telles que des conditions d'erreur; par conséquent, la
Mb
structure de résultat est généralement très utile. Par exemple, l'opérateur de division ne renvoie pas de nombre lorsque le diviseur l'est0
.De plus,
monad
s peut inclure des fonctions d'enveloppement qui encapsulent des valeurs,a
dans le type monadiqueMa
, et des fonctions généralesa -> b
, dans des fonctions monadiquesa -> Mb
, en encapsulant leurs résultats après application. Bien sûr, commebind
, ces fonctions de bouclage sont spécifiques àM
. Un exemple:La conception de la
bind
fonction suppose des structures de données immuables et des fonctions pures, d'autres choses deviennent complexes et des garanties ne peuvent être apportées. À ce titre, il existe des lois monadiques:Donné...
Alors...
Associativity
signifie quebind
préserve l'ordre d'évaluation indépendamment du moment où ilbind
est appliqué. C'est-à-dire, dans la définition deAssociativity
ci-dessus, forcer l'évaluation précoce de la parenthèsebinding
def
etg
n'aboutira qu'à une fonction qui attendMa
afin de terminer lebind
. Par conséquent, l'évaluation deMa
doit être déterminée avant que sa valeur puisse être appliquéef
et que le résultat à son tour soit appliqué àg
.la source
Une monade est, en fait, une forme "d'opérateur de type". Cela fera trois choses. Tout d'abord, il "encapsulera" (ou convertira d'une autre manière) une valeur d'un type en un autre type (généralement appelé "type monadique"). Deuxièmement, il rendra toutes les opérations (ou fonctions) disponibles sur le type sous-jacent disponibles sur le type monadique. Enfin, il fournira un support pour la combinaison de son auto avec une autre monade pour produire une monade composite.
Le «peut-être monade» est essentiellement l'équivalent de «types nullables» dans Visual Basic / C #. Il prend un type non nullable "T" et le convertit en "Nullable <T>", puis définit ce que tous les opérateurs binaires signifient sur un Nullable <T>.
Les effets secondaires sont représentés de façon similaire. Une structure est créée qui contient des descriptions des effets secondaires à côté de la valeur de retour d'une fonction. Les opérations "levées" copient ensuite les effets secondaires au fur et à mesure que les valeurs sont transmises entre les fonctions.
Ils sont appelés "monades" plutôt que le nom plus facile à saisir des "opérateurs de type" pour plusieurs raisons:
la source
(Voir aussi les réponses sur Qu'est-ce qu'une monade? )
Une bonne motivation pour les Monades est Vous pourriez avoir inventé les Monades de sigfpe (Dan Piponi) ! (Et peut-être que vous en avez déjà) . Il existe BEAUCOUP d'autres didacticiels sur les monades, dont beaucoup tentent à tort d'expliquer les monades en "termes simples" en utilisant diverses analogies: c'est l' erreur du tutoriel sur les monades ; évite-les.
Comme le dit DR MacIver dans Dites-nous pourquoi votre langue craint :
Vous dites que vous comprenez la monade peut-être? Bon, vous êtes en route. Commencez simplement à utiliser d'autres monades et tôt ou tard, vous comprendrez ce que sont les monades en général.
[Si vous êtes orienté mathématique, vous voudrez peut-être ignorer les dizaines de tutoriels et apprendre la définition, ou suivre des conférences sur la théorie des catégories :) La partie principale de la définition est qu'une Monade M implique un "constructeur de type" qui définit pour chaque type existant "T" un nouveau type "MT", et quelques façons de faire la navette entre les types "réguliers" et les types "M".]
Aussi, de façon assez surprenante, l'une des meilleures introductions aux monades est en fait l'un des premiers articles universitaires présentant les monades, les Monades de Philip Wadler pour la programmation fonctionnelle . Il contient en fait des exemples de motivation pratiques et non triviaux , contrairement à de nombreux didacticiels artificiels.
la source
Les monades doivent contrôler le flux des types de données abstraits vers les données.
En d'autres termes, de nombreux développeurs sont à l'aise avec l'idée des ensembles, des listes, des dictionnaires (ou des hachages ou des cartes) et des arbres. Au sein de ces types de données, il existe de nombreux cas particuliers (par exemple InsertionOrderPreservingIdentityHashMap).
Cependant, lorsqu'ils sont confrontés au "flux" de programmes, de nombreux développeurs n'ont pas été exposés à beaucoup plus de constructions que si, switch / case, do, while, goto (grr) et (peut-être) fermetures.
Ainsi, une monade est simplement une construction de flux de contrôle. Une meilleure expression pour remplacer la monade serait «type de contrôle».
En tant que tel, une monade a des emplacements pour la logique de contrôle, ou des instructions ou des fonctions - l'équivalent dans les structures de données serait de dire que certaines structures de données vous permettent d'ajouter des données et de les supprimer.
Par exemple, la monade "if":
à son plus simple a deux emplacements - une clause et un bloc. La
if
monade est généralement construite pour évaluer le résultat de la clause et, si elle n'est pas fausse, pour évaluer le bloc. De nombreux développeurs ne sont pas initiés aux monades lorsqu'ils apprennent «si», et il n'est tout simplement pas nécessaire de comprendre les monades pour écrire une logique efficace.Les monades peuvent devenir plus compliquées, de la même manière que les structures de données peuvent devenir plus compliquées, mais il existe de nombreuses grandes catégories de monades qui peuvent avoir une sémantique similaire, mais des implémentations et une syntaxe différentes.
Bien sûr, de la même manière que les structures de données peuvent être itérées ou traversées, les monades peuvent être évaluées.
Les compilateurs peuvent ou non prendre en charge les monades définies par l'utilisateur. Haskell le fait certainement. Ioke a des capacités similaires, bien que le terme monade ne soit pas utilisé dans le langage.
la source
Mon tutoriel Monad préféré:
http://www.haskell.org/haskellwiki/All_About_Monads
(sur 170 000 visites sur une recherche Google pour "tutoriel monade"!)
@Stu: L'intérêt des monades est de vous permettre d'ajouter (généralement) une sémantique séquentielle à du code par ailleurs pur; vous pouvez même composer des monades (en utilisant des transformateurs de monade) et obtenir une sémantique combinée plus intéressante et compliquée, comme l'analyse avec la gestion des erreurs, l'état partagé et la journalisation, par exemple. Tout cela est possible en code pur, les monades vous permettent simplement de l'abstraire et de le réutiliser dans des bibliothèques modulaires (toujours bonnes en programmation), ainsi que de fournir une syntaxe pratique pour la rendre impérative.
Haskell a déjà une surcharge d'opérateur [1]: il utilise des classes de type à peu près comme on pourrait utiliser des interfaces en Java ou C # mais Haskell se trouve juste à autoriser également des jetons non alphanumériques comme + && et> comme identificateurs d'infixe. C'est seulement une surcharge d'opérateur dans votre façon de voir les choses si vous voulez dire "surcharger le point-virgule" [2]. Cela ressemble à de la magie noire et demande des ennuis pour "surcharger le point-virgule" (imaginez les pirates de Perl entreprenants obtenant le vent de cette idée) mais le fait est que sans monades, il n'y a pas de point-virgule, car le code purement fonctionnel ne nécessite ni ne permet un séquencement explicite.
Tout cela semble beaucoup plus compliqué que nécessaire. L'article de sigfpe est assez cool mais utilise Haskell pour l'expliquer, ce qui ne résout pas le problème du poulet et des œufs de comprendre Haskell to grok Monads et de comprendre Monads to grok Haskell.
[1] Il s'agit d'un problème distinct des monades, mais les monades utilisent la fonction de surcharge d'opérateur de Haskell.
[2] C'est aussi une simplification excessive puisque l'opérateur de chaînage des actions monadiques est >> = (prononcé "bind") mais il y a du sucre syntaxique ("do") qui vous permet d'utiliser des accolades et des points-virgules et / ou une indentation et des sauts de ligne.
la source
J'ai pensé aux Monades d'une manière différente, ces derniers temps. Je les ai considérés comme l'abstraction de l' ordre d'exécution d'une manière mathématique, ce qui rend possible de nouveaux types de polymorphisme.
Si vous utilisez un langage impératif et que vous écrivez certaines expressions dans l'ordre, le code s'exécute TOUJOURS exactement dans cet ordre.
Et dans le cas simple, lorsque vous utilisez une monade, c'est la même chose - vous définissez une liste d'expressions qui se produisent dans l'ordre. Sauf que, selon la monade que vous utilisez, votre code peut s'exécuter dans l'ordre (comme dans la monade IO), en parallèle sur plusieurs éléments à la fois (comme dans la monade de liste), il peut s'arrêter à mi-chemin (comme dans la monade peut-être) , il peut s'arrêter à mi-chemin pour être repris plus tard (comme dans une monade de reprise), il peut revenir en arrière et recommencer depuis le début (comme dans une monade de transaction), ou il peut revenir en arrière à mi-chemin pour essayer d'autres options (comme dans une monade logique) .
Et comme les monades sont polymorphes, il est possible d'exécuter le même code dans différentes monades, selon vos besoins.
De plus, dans certains cas, il est possible de combiner des monades (avec des transformateurs de monade) pour obtenir plusieurs fonctionnalités en même temps.
la source
Je suis encore nouveau dans les monades, mais j'ai pensé partager un lien que j'ai trouvé très agréable à lire (AVEC DES PHOTOS !!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monades-pour-le-profane / (aucune affiliation)
Fondamentalement, le concept chaleureux et flou que j'ai obtenu de l'article était le concept que les monades sont essentiellement des adaptateurs qui permettent à des fonctions disparates de fonctionner de manière composable, c'est-à-dire de pouvoir enchaîner plusieurs fonctions et les mélanger et les assortir sans se soucier d'un retour incohérent types et autres. La fonction BIND est donc chargée de conserver les pommes avec les pommes et les oranges avec les oranges lorsque nous essayons de fabriquer ces adaptateurs. Et la fonction LIFT est chargée de prendre les fonctions de "niveau inférieur" et de les "mettre à jour" pour qu'elles fonctionnent avec les fonctions BIND et soient également composables.
J'espère que j'ai bien compris, et plus important encore, j'espère que l'article a une vue valable sur les monades. Si rien d'autre, cet article m'a aidé à me mettre en appétit pour en savoir plus sur les monades.
la source
En plus des excellentes réponses ci-dessus, permettez-moi de vous offrir un lien vers l'article suivant (par Patrick Thomson) qui explique les monades en reliant le concept à la bibliothèque JavaScript jQuery (et sa façon d'utiliser le "chaînage de méthodes" pour manipuler le DOM) : jQuery est une monade
La documentation jQuery elle-même ne fait pas référence au terme "monade" mais parle du "modèle de générateur" qui est probablement plus familier. Cela ne change pas le fait que vous ayez une bonne monade là-bas sans même vous en rendre compte.
la source
Les monades ne sont pas des métaphores , mais une abstraction pratiquement utile émergeant d'un modèle commun, comme l'explique Daniel Spiewak.
la source
Une monade est un moyen de combiner des calculs qui partagent un contexte commun. C'est comme construire un réseau de tuyaux. Lors de la construction du réseau, aucune donnée ne le traverse. Mais quand j'ai fini d'assembler tous les bits avec «bind» et «return», j'invoque quelque chose comme
runMyMonad monad data
et les données circulent à travers les tuyaux.la source
En pratique, monad est une implémentation personnalisée de l'opérateur de composition de fonctions qui prend en charge les effets secondaires et les valeurs d'entrée et de retour incompatibles (pour le chaînage).
la source
Si j'ai bien compris, IEnumerable est dérivé de monades. Je me demande si cela pourrait être un angle d'approche intéressant pour ceux d'entre nous du monde C #?
Pour ce que ça vaut, voici quelques liens vers des tutoriels qui m'ont aidé (et non, je n'ai toujours pas compris ce que sont les monades).
la source
Les deux choses qui m'ont le plus aidé lors de mon apprentissage étaient:
Chapitre 8, "Functional Parsers", du livre de Graham Hutton Programming in Haskell . En fait, cela ne mentionne pas du tout les monades, mais si vous pouvez parcourir le chapitre et vraiment tout comprendre, en particulier comment une séquence d'opérations de liaison est évaluée, vous comprendrez les internes des monades. Attendez-vous à ce que cela prenne plusieurs essais.
Le tutoriel Tout sur les monades . Cela donne plusieurs bons exemples de leur utilisation, et je dois dire que l'analogie avec Appendex a fonctionné pour moi.
la source
Monoïde semble être quelque chose qui garantit que toutes les opérations définies sur un monoïde et un type pris en charge renverront toujours un type pris en charge à l'intérieur du monoïde. Par exemple, Tout nombre + Tout nombre = Un nombre, pas d'erreurs.
Alors que la division accepte deux fractionnaires et renvoie un fractionnaire, qui définit la division par zéro comme Infinity dans haskell somewhy (qui se trouve être un fractionnaire somewhy) ...
Dans tous les cas, il semble que les monades ne soient qu'un moyen de garantir que votre chaîne d'opérations se comporte de manière prévisible, et qu'une fonction qui prétend être Num -> Num, composée d'une autre fonction de Num-> Num appelée avec x ne fonctionne pas disons, tirez les missiles.
D'un autre côté, si nous avons une fonction qui tire les missiles, nous pouvons la composer avec d'autres fonctions qui tirent également les missiles, parce que notre intention est claire - nous voulons tirer les missiles - mais elle n'essaiera pas imprimer "Hello World" pour une raison étrange.
Dans Haskell, main est de type IO (), ou IO [()], la distiction est étrange et je n'en discuterai pas mais voici ce qui se passe:
Si j'ai un principal, je veux qu'il fasse une chaîne d'actions, la raison pour laquelle je lance le programme est de produire un effet - généralement via IO. Ainsi, je peux enchaîner les opérations d'E / S en mode principal pour - faire des E / S, rien d'autre.
Si j'essaye de faire quelque chose qui ne "retourne pas IO", le programme se plaindra que la chaîne ne coule pas, ou fondamentalement "Comment cela se rapporte-t-il à ce que nous essayons de faire - une action IO", cela semble forcer le programmeur à garder son train de pensée, sans s'éloigner et penser à tirer les missiles, tout en créant des algorithmes de tri - qui ne coulent pas.
Fondamentalement, les monades semblent être une astuce pour le compilateur: "Hé, vous connaissez cette fonction qui renvoie un nombre ici, elle ne fonctionne pas toujours, elle peut parfois produire un nombre, et parfois rien du tout, conservez-la simplement dans esprit". Sachant cela, si vous essayez d'affirmer une action monadique, l'action monadique peut agir comme une exception de temps de compilation en disant "hé, ce n'est pas vraiment un nombre, cela PEUT être un nombre, mais vous ne pouvez pas le supposer, faites quelque chose pour garantir que le débit est acceptable. " ce qui empêche le comportement imprévisible du programme - dans une certaine mesure.
Il semble que les monades ne concernent pas la pureté, ni le contrôle, mais le maintien de l'identité d'une catégorie sur laquelle tout comportement est prévisible et défini, ou ne se compile pas. Vous ne pouvez rien faire lorsque vous êtes censé faire quelque chose, et vous ne pouvez pas faire quelque chose si vous ne devez rien faire (visible).
La plus grande raison pour laquelle je pourrais penser à Monads est - allez voir le code Procédural / OOP, et vous remarquerez que vous ne savez pas où le programme commence ni se termine, tout ce que vous voyez est beaucoup de sauts et beaucoup de mathématiques , la magie et les missiles. Vous ne pourrez pas le maintenir, et si vous le pouvez, vous passerez beaucoup de temps à vous concentrer sur tout le programme avant de pouvoir en comprendre une partie, car la modularité dans ce contexte est basée sur des "sections" interdépendantes. du code, où le code est optimisé pour être aussi lié que possible pour la promesse d'efficacité / inter-relation. Les monades sont très concrètes et bien définies par définition, et garantissent que le flux du programme est possible d'analyser et d'isoler les parties qui sont difficiles à analyser - car elles sont elles-mêmes des monades. Une monade semble être un " ou détruire l'univers ou même déformer le temps - nous n'avons aucune idée ni aucune garantie que C'EST CE QU'IL EST. Une monade GARANTIT QUE C'EST CE QU'IL EST. ce qui est très puissant. ou détruire l'univers ou même déformer le temps - nous n'avons aucune idée ni aucune garantie que C'EST CE QU'IL EST. Une monade GARANTIT QUE C'EST CE QU'IL EST. ce qui est très puissant.
Toutes les choses dans le «monde réel» semblent être des monades, dans le sens où elles sont liées par des lois observables définies empêchant la confusion. Cela ne signifie pas que nous devons imiter toutes les opérations de cet objet pour créer des classes, mais nous pouvons simplement dire "un carré est un carré", rien qu'un carré, pas même un rectangle ni un cercle, et "un carré a une aire de la longueur d'une de ses dimensions existantes multipliée par elle-même. Peu importe le carré que vous avez, si c'est un carré dans l'espace 2D, sa surface ne peut absolument pas être autre que sa longueur au carré, c'est presque trivial à prouver. C'est très puissant car nous n'avons pas besoin de faire des affirmations pour nous assurer que notre monde est tel qu'il est, nous utilisons simplement les implications de la réalité pour empêcher nos programmes de dévier.
Je suis presque sûr d'avoir tort, mais je pense que cela pourrait aider quelqu'un là-bas, alors j'espère que cela aidera quelqu'un.
la source
Dans le contexte de Scala, vous trouverez ce qui suit comme la définition la plus simple. Fondamentalement, flatMap (ou bind) est «associatif» et il existe une identité.
Par exemple
REMARQUE À strictement parler, la définition d'une monade dans la programmation fonctionnelle n'est pas la même que la définition d'une monade dans la théorie des catégories , qui est définie dans les tours de
map
etflatten
. Bien qu'ils soient en quelque sorte équivalents dans certains mappages. Ces présentations sont très bonnes: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-categoryla source
Cette réponse commence par un exemple motivant, passe par l'exemple, dérive un exemple de monade et définit formellement "monade".
Considérez ces trois fonctions dans le pseudocode:
f
prend une paire ordonnée du formulaire<x, messages>
et retourne une paire ordonnée. Il laisse le premier élément intact et ajoute"called f. "
au deuxième élément. Même chose avecg
.Vous pouvez composer ces fonctions et obtenir votre valeur d'origine, ainsi qu'une chaîne qui indique l'ordre dans lequel les fonctions ont été appelées:
Vous n'aimez pas le fait
f
etg
êtes responsable de l'ajout de leurs propres messages de journal aux informations de journalisation précédentes. (Imaginez juste pour l'argument qu'au lieu d'ajouter des chaînes,f
etg
doit effectuer une logique compliquée sur le deuxième élément de la paire. Ce serait pénible de répéter cette logique compliquée dans deux - ou plus - fonctions différentes.)Vous préférez écrire des fonctions plus simples:
Mais regardez ce qui se passe lorsque vous les composez:
Le problème est que passer une paire dans une fonction ne vous donne pas ce que vous voulez. Et si vous pouviez alimenter une paire dans une fonction:
Lire
feed(f, m)
comme « l' alimentationm
enf
». Pour alimenter une paire<x, messages>
en fonctionf
est de passerx
dansf
, se<y, message>
sortir def
et retour<y, messages message>
.Remarquez ce qui se passe lorsque vous faites trois choses avec vos fonctions:
Tout d' abord: si vous enveloppez une valeur et nourrir la paire résultant en une fonction:
Cela revient à passer la valeur à la fonction.
Deuxièmement: si vous introduisez une paire dans
wrap
:Cela ne change pas la paire.
Troisièmement: si vous définissez une fonction qui prend
x
et se nourritg(x)
dansf
:et introduisez-y une paire:
Cela revient à alimenter la paire
g
et à alimenter la paire résultantef
.Vous avez presque une monade. Il vous suffit maintenant de connaître les types de données de votre programme.
De quel type de valeur s'agit-il
<x, "called f. ">
? Eh bien, cela dépend du type de valeurx
. Six
est de typet
, alors votre paire est une valeur de type "paire det
et chaîne". Appelez ce typeM t
.M
est un constructeur de type:M
seul ne fait pas référence à un type, maisM _
fait référence à un type une fois que vous avez rempli le blanc avec un type. AnM int
est une paire d'int et de chaîne. AnM string
est une paire d'une chaîne et d'une chaîne. Etc.Félicitations, vous avez créé une monade!
Officiellement, votre monade est le tuple
<M, feed, wrap>
.Une monade est un tuple
<M, feed, wrap>
où:M
est un constructeur de type.feed
prend une (fonction qui prend unt
et retourne unM u
) et unM t
et retourne unM u
.wrap
prend unv
et retourne unM v
.t
,,u
etv
sont trois types qui peuvent être identiques ou non. Une monade satisfait les trois propriétés que vous avez prouvées pour votre monade spécifique:Alimenter un enveloppé
t
dans une fonction équivaut à passer le déroulét
dans la fonction.Officiellement:
feed(f, wrap(x)) = f(x)
Nourrir un
M t
danswrap
ne fait rien pour leM t
.Officiellement:
feed(wrap, m) = m
Alimenter un
M t
(appelez-lem
) dans une fonction quit
dansg
M u
(appelez-len
) deg
n
enf
est le même que
m
dansg
n
deg
n
dansf
Formellement:
feed(h, m) = feed(f, feed(g, m))
oùh(x) := feed(f, g(x))
En règle générale,
feed
est appelébind
(AKA>>=
dans Haskell) etwrap
est appeléreturn
.la source
Je vais essayer d'expliquer
Monad
dans le contexte de Haskell.Dans la programmation fonctionnelle, la composition des fonctions est importante. Il permet à notre programme de se composer de petites fonctions faciles à lire.
Disons que nous avons deux fonctions:
g :: Int -> String
etf :: String -> Bool
.Nous pouvons faire
(f . g) x
, ce qui est exactement la même chose quef (g x)
, oùx
est uneInt
valeur.Lors de la composition / application du résultat d'une fonction à une autre, il est important de faire correspondre les types. Dans le cas ci-dessus, le type du résultat retourné par
g
doit être le même que le type accepté parf
.Mais parfois, les valeurs sont dans des contextes, ce qui rend un peu moins facile l'alignement des types. (Avoir des valeurs dans des contextes est très utile. Par exemple, le
Maybe Int
type représente uneInt
valeur qui peut ne pas être là, leIO String
type représente uneString
valeur qui est là suite à l'exécution de certains effets secondaires.)Disons que nous avons maintenant
g1 :: Int -> Maybe String
etf1 :: String -> Maybe Bool
.g1
etf1
sont très similaires àg
etf
respectivement.Nous ne pouvons pas faire
(f1 . g1) x
ouf1 (g1 x)
, oùx
est uneInt
valeur. Le type du résultat renvoyé parg1
n'est pas celuif1
attendu.Nous pourrions composer
f
etg
avec l'.
opérateur, mais maintenant nous ne pouvons pas composerf1
etg1
avec.
. Le problème est que nous ne pouvons pas transmettre directement une valeur dans un contexte à une fonction qui attend une valeur qui n'est pas dans un contexte.Ne serait-il pas agréable d'introduire un opérateur pour composer
g1
etf1
, de sorte que nous puissions écrire(f1 OPERATOR g1) x
?g1
renvoie une valeur dans un contexte. La valeur sera prise hors contexte et appliquée àf1
. Et oui, nous avons un tel opérateur. Ça l'est<=<
.Nous avons également l'
>>=
opérateur qui fait pour nous exactement la même chose, mais dans une syntaxe légèrement différente.Nous écrivons:
g1 x >>= f1
.g1 x
est uneMaybe Int
valeur. L'>>=
opérateur aide à retirer cetteInt
valeur du contexte «peut-être pas là» et à l'appliquerf1
. Le résultat def1
, qui est unMaybe Bool
, sera le résultat de toute l'>>=
opération.Et enfin, pourquoi est-ce
Monad
utile? Parce queMonad
c'est la classe de type qui définit l'>>=
opérateur, très similaire à laEq
classe de type qui définit les opérateurs==
et/=
.Pour conclure, la
Monad
classe type définit l'>>=
opérateur qui nous permet de passer des valeurs dans un contexte (nous appelons ces valeurs monadiques) à des fonctions qui n'attendent pas de valeurs dans un contexte. Le contexte sera pris en charge.S'il y a une chose à retenir ici, c'est qu'elle
Monad
permet la composition de fonctions qui implique des valeurs dans des contextes .la source
tl; dr
Prologue
L'opérateur d'application
$
des fonctionsest défini canoniquement
en termes d'application de la fonction primitive de Haskell
f x
(infixl 10
).Composition
.
est définie en termes de$
commeet satisfait les équivalences
forall f g h.
.
est associative, etid
est son identité droite et gauche.Le triple Kleisli
En programmation, une monade est un constructeur de type foncteur avec une instance de la classe de type monade. Il existe plusieurs variantes équivalentes de définition et de mise en œuvre, chacune portant des intuitions légèrement différentes sur l'abstraction de la monade.
Un functor est un constructeur
f
de type de type* -> *
avec une instance de la classe de type functor.En plus de suivre le protocole de type appliqué statiquement, les instances de la classe de type foncteur doivent obéir aux lois algébriques des foncteurs
forall f g.
Les calculs de foncteurs ont le type
Un calcul
c r
consiste en des résultatsr
dans le contextec
.Les fonctions monaires unaires ou les flèches de Kleisli ont le type
Les flèches Kleisi sont des fonctions qui prennent un argument
a
et retournent un calcul monadiquem b
.Les monades sont définies canoniquement en fonction du triple de Kleisli
forall m. Functor m =>
implémenté comme classe de type
L' identité Kleisli
return
est une flèche Kleisli qui promeut une valeurt
dans un contexte monadiquem
. L'extension ou l' application Kleisli=<<
applique une flèche Kleislia -> m b
aux résultats d'un calculm a
.La composition de Kleisli
<=<
est définie en termes d'extension comme<=<
compose deux flèches Kleisli, en appliquant la flèche gauche aux résultats de l'application de la flèche droite.Les instances de la classe de type monade doivent obéir aux lois de la monade , le plus élégamment énoncées en termes de composition de Kleisli:
forall f g h.
<=<
est associative, etreturn
est son identité droite et gauche.Identité
Le type d'identité
est la fonction d'identité sur les types
Interprété comme foncteur,
Dans Haskell canonique, la monade d'identité est définie
Option
Un type d'option
code un calcul
Maybe t
qui ne donne pas nécessairement un résultatt
, un calcul qui peut «échouer». L'option monade est définiea -> Maybe b
n'est appliqué à un résultat que s'ilMaybe a
donne un résultat.Les nombres naturels peuvent être codés comme ces entiers supérieurs ou égaux à zéro.
Les nombres naturels ne sont pas fermés par soustraction.
L'option monad couvre une forme de base de gestion des exceptions.
liste
La liste monad, sur le type de liste
et son fonctionnement monoïde additif "append"
code un calcul non linéaire
[t]
produisant une quantité naturelle0, 1, ...
de résultatst
.L'extension
=<<
concatène++
toutes les listes[b]
résultant des applicationsf x
d'une flèche de Kleislia -> [b]
aux éléments de[a]
dans une seule liste de résultats[b]
.Que les diviseurs propres d'un entier positif
n
soientpuis
Pour définir la classe de type monade, au lieu de l'extension
=<<
, la norme Haskell utilise son flip, l' opérateur de liaison>>=
.Par souci de simplicité, cette explication utilise la hiérarchie des classes de types
Dans Haskell, la hiérarchie standard actuelle est
car non seulement chaque monade est un foncteur, mais chaque applicatif est un foncteur et chaque monade est également un applicatif.
En utilisant la liste monad, le pseudocode impératif
se traduit approximativement par le bloc do ,
la compréhension équivalente de la monade ,
et l'expression
La notation Do et les compréhensions monade sont des éléments syntaxiques pour les expressions de liaison imbriquées. L'opérateur de liaison est utilisé pour la liaison de nom local des résultats monadiques.
où
La fonction de garde est définie
où le type d'unité ou «tuple vide»
Les monades additives qui prennent en charge le choix et l' échec peuvent être abstraites à l'aide d'une classe de type
où
fail
et<|>
former un monoïdeforall k l m.
et
fail
est l'élément zéro absorbant / annihilant des monades additivesSi dans
even p
est vrai, alors la garde produit[()]
, et, par la définition de>>
, la fonction constante localeest appliqué au résultat
()
. Si faux, le garde produit la liste monadfail
([]
), qui ne donne aucun résultat pour qu'une flèche de Kleisli soit appliquée>>
, donc celap
est ignoré.Etat
Malheureusement, les monades sont utilisées pour coder le calcul avec état.
Un processeur d'état est une fonction
qui transite un état
st
et donne un résultatt
. L' Étatst
peut être n'importe quoi. Rien, drapeau, compte, tableau, poignée, machine, monde.Le type de processeur d'État est généralement appelé
La monade du processeur d'état est le
* -> *
foncteur du genreState st
. Les flèches de Kleisli de la monade du processeur d'état sont des fonctionsDans Haskell canonique, la version paresseuse de la monade de processeur d'état est définie
Un processeur d'état est exécuté en fournissant un état initial:
L'accès à l'État est fourni par des primitives
get
et desput
méthodes d'abstraction sur des monades avec état :m -> st
déclare une dépendance fonctionnelle du type d'étatst
sur la monadem
; qu'unState t
, par exemple, déterminera le type d'état de manièret
unique.avec le type d'unité utilisé de manière analogue à
void
en C.gets
est souvent utilisé avec les accesseurs de champ d'enregistrement.L'équivalent monade d'état du thread variable
où
s0 :: Int
, est le tout aussi référentiellement transparent, mais infiniment plus élégant et pratiquemodify (+ 1)
est un calcul de typeState Int ()
, sauf pour son effet équivalent àreturn ()
.La loi monade de l'associativité peut être écrite en termes de
>>=
forall m f g.
ou
Comme dans la programmation orientée expression (par exemple Rust), la dernière instruction d'un bloc représente son rendement. L'opérateur de liaison est parfois appelé «point-virgule programmable».
Les primitives de structure de contrôle d'itération de la programmation impérative structurée sont émulées de façon monadique
Entrée sortie
La monade de processeur d'état mondial d'E / S est une réconciliation de Haskell pur et du monde réel, de sémantique opérationnelle dénotative et impérative fonctionnelle. Un analogue proche de la mise en œuvre stricte réelle:
L'interaction est facilitée par des primitives impures
L'impureté du code qui utilise des
IO
primitives est protocoleée en permanence par le système de type. Parce que la pureté est géniale, ce qui se passe enIO
resteIO
.Ou, du moins, devrait.
La signature de type d'un programme Haskell
s'étend à
Une fonction qui transforme un monde.
Épilogue
La catégorie des objets qui sont des types Haskell et des morphismes qui sont des fonctions entre les types Haskell est, "rapide et lâche", la catégorie
Hask
.Un foncteur
T
est un mappage d'une catégorieC
à une catégorieD
; pour chaque objet dansC
un objetD
et pour chaque morphisme dans
C
un morphismeD
où
X
,Y
sont les objetsC
.HomC(X, Y)
est la classe d'homomorphisme de tous les morphismesX -> Y
enC
. Le foncteur doit préserver l'identité et la composition du morphisme, la «structure» deC
, enD
.La catégorie Kleisli d'une catégorie
C
est donnée par un triple Kleislid'un endofuncteur
(
f
), un morphisme d'identitéeta
(return
) et un opérateur d'extension*
(=<<
).Chaque morphisme de Kleisli dans
Hask
par l'opérateur d'extension
reçoit un morphisme dans
Hask
la catégorie de KleisliLa composition dans la catégorie Kleisli
.T
est donnée en termes d'extensionet satisfait les axiomes de catégorie
qui, en appliquant les transformations d'équivalence
en termes d'extension sont donnés canoniquement
Les monades peuvent également être définies en termes non pas d'extension kleislienne, mais d'une transformation naturelle
mu
, appelée programmationjoin
. Une monade est définie en termes demu
triple sur une catégorieC
, d'un endofoncteuret deux transformations naturelles
satisfaire les équivalences
La classe de type monade est alors définie
L'
mu
implémentation canonique de l'option monade:La
concat
fonctionest la
join
monade de la liste.Les implémentations de
join
peuvent être traduites à partir du formulaire d'extension en utilisant l'équivalenceLa traduction inverse de
mu
vers extension est donnée parPhilip Wadler: Monades pour la programmation fonctionnelle
Simon L Peyton Jones, Philip Wadler: programmation fonctionnelle impérative
Jonathan MD Hill, Keith Clarke: Introduction à la théorie des catégories, aux monades de la théorie des catégories et à leur relation avec la programmation fonctionnelle ´
Catégorie Kleisli
Eugenio Moggi: Notions de calcul et monades
Ce qu'une monade n'est pas
de la généralisation des monades aux flèches par John Hughes
la source
Ce dont le monde a besoin, c'est d'un autre article de blog sur la monade, mais je pense que cela est utile pour identifier les monades existantes dans la nature.
la source
http://code.google.com/p/monad-tutorial/ est un travail en cours pour répondre exactement à cette question.
la source
Laissez le "
{| a |m}
" ci-dessous représenter un morceau de données monadiques. Un type de données qui annonce una
:Fonction,,
f
sait créer une monade, si seulement elle avait una
:Ici, nous voyons la fonction
f
,, essaie d'évaluer une monade mais est réprimandée.Funtion,,
f
trouve un moyen d'extraire lea
en utilisant>>=
.Peu
f
sait, la monade et>>=
sont en collusion.Mais de quoi parlent-ils réellement? Eh bien, cela dépend de la monade. Parler uniquement dans l'abstrait a une utilisation limitée; vous devez avoir une certaine expérience avec des monades particulières pour étoffer la compréhension.
Par exemple, le type de données Maybe
a une instance de monade qui agira comme suit ...
Où, si l'affaire est
Just a
Mais pour le cas de
Nothing
Ainsi, la monade Maybe permet à un calcul de continuer s'il contient réellement ce
a
qu'il annonce, mais abandonne le calcul s'il ne le fait pas. Le résultat, cependant, est toujours un morceau de données monadiques, mais pas la sortie def
. Pour cette raison, la monade Maybe est utilisée pour représenter le contexte de l'échec.Différentes monades se comportent différemment. Les listes sont d'autres types de données avec des instances monadiques. Ils se comportent comme suit:
Dans ce cas, la fonction savait comment créer une liste à partir de son entrée, mais ne savait pas quoi faire avec une entrée et des listes supplémentaires. Le bind
>>=
, aidéf
en combinant les multiples sorties. J'inclus cet exemple pour montrer que s'il>>=
est responsable de l'extractiona
, il a également accès à la sortie liée finale def
. En effet, il n'en extraira jamais àa
moins qu'il ne sache que la sortie éventuelle a le même type de contexte.Il existe d'autres monades qui sont utilisées pour représenter différents contextes. Voici quelques caractérisations de quelques autres. La
IO
monade n'en a pasa
, mais elle connaît un mec et elle en auraa
pour vous. LaState st
monade a une cachette secrètest
qu'elle passeraf
sous la table, même sif
je viens juste de demander una
. LaReader r
monade est similaire àState st
, bien qu'elle ne laisse quef
regarderr
.Le point dans tout cela est que tout type de données qui est déclaré être une monade déclare une sorte de contexte autour de l'extraction d'une valeur de la monade. Le gros gain de tout ça? Eh bien, c'est assez facile de formuler un calcul avec une sorte de contexte. Cependant, cela peut devenir compliqué lors de l'enchaînement de plusieurs calculs chargés de contexte. Les opérations monad prennent soin de résoudre les interactions de contexte afin que le programmeur n'ait pas à le faire.
Notez que cette utilisation
>>=
facilite le désordre en retirant une partie de l'autonomief
. C'est-à-dire, dans le cas ci-dessusNothing
par exemple,f
ne peut plus décider quoi faire dans le cas deNothing
; il est encodé>>=
. C'est le compromis. S'il était nécessairef
de décider quoi faire dans le cas deNothing
, alors celaf
aurait dû être une fonction deMaybe a
àMaybe b
. Dans ce cas,Maybe
être une monade n'a pas d'importance.Notez, cependant, que parfois un type de données n'exporte pas ses constructeurs (en vous regardant IO), et si nous voulons travailler avec la valeur publiée, nous n'avons pas d'autre choix que de travailler avec son interface monadique.
la source
Une monade est une chose utilisée pour encapsuler des objets dont l'état change. Il est le plus souvent rencontré dans les langues qui autrement ne vous permettent pas d'avoir un état modifiable (par exemple, Haskell).
Un exemple serait pour les E / S de fichiers.
Vous seriez en mesure d'utiliser une monade pour les E / S de fichiers pour isoler la nature de l'état changeant au seul code qui a utilisé la monade. Le code à l'intérieur de la Monade peut effectivement ignorer l'état changeant du monde à l'extérieur de la Monade - cela facilite beaucoup la réflexion sur l'effet global de votre programme.
la source