Qu'est-ce qu'une monade?


1416

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.


12
Eric Lippert a écrit une réponse à ces questions ( stackoverflow.com/questions/2704652/… ), qui est due à certains problèmes vit dans une page séparée.
P Shved

70
Voici une nouvelle introduction en utilisant javascript - je l'ai trouvé très lisible.
Benjol



2
Une monade est un tableau de fonctions avec des opérations d'assistance. Voir cette réponse
cibercitizen1

Réponses:


1060

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 :

[x*2 | x<-[1..10], odd x]

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:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

Ou même:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Exemple 2: entrée / sortie :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

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 Errormonade, 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:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Les opérations char, digitetc. 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 :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

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 asyncmonade est capable de "diviser" le bloc par elle-même et de reporter l'exécution de cette dernière moitié. (La async {}syntaxe indique que le flux de contrôle dans le bloc est défini par la asyncmonade.)

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' bindopérateur (orthographié >>=en Haskell). Étant donné que l' bindopé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:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

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


66
En tant que personne qui a eu beaucoup de problèmes à comprendre les monades, je peux dire que cette réponse a un peu aidé. Cependant, il y a encore des choses que je ne comprends pas. En quoi la compréhension de la liste est-elle une monade? Existe-t-il une forme élargie de cet exemple? Une autre chose qui me dérange vraiment dans la plupart des explications de la monade, y compris celle-ci: est-ce qu'elles continuent à mélanger "qu'est-ce qu'une monade?" avec "à quoi sert une monade?" et "Comment une monade est-elle implémentée?". vous avez sauté ce requin lorsque vous avez écrit "Une monade est fondamentalement juste un type qui prend en charge l'opérateur >> =." Qui vient de m'avoir ...
Breton

83
Je suis également en désaccord avec votre conclusion sur les raisons pour lesquelles les monades sont difficiles. Si les monades elles-mêmes ne sont pas complexes, vous devriez pouvoir expliquer ce qu'elles sont sans un tas de bagages. Je ne veux pas en savoir plus sur l'implémentation lorsque je pose la question "Qu'est-ce qu'une monade", je veux savoir ce que ça veut dire gratter. Jusqu'à présent, il semble que la réponse soit "Parce que les auteurs de haskell sont des sadomasochistes et ont décidé que vous devriez faire quelque chose de stupidement complexe pour accomplir des choses simples, donc vous DEVEZ apprendre des monades à utiliser haskell, pas parce qu'elles sont en aucune façon utiles dans eux-mêmes "...
Breton

70
Mais .. ça ne peut pas être vrai, n'est-ce pas? Je pense que les monades sont difficiles parce que personne ne semble comprendre comment les expliquer sans se laisser entraîner dans des détails d'implémentation confus. Je veux dire .. qu'est-ce qu'un bus scolaire? C'est une plate-forme métallique avec un appareil à l'avant qui consomme un produit pétrolier raffiné pour entraîner dans un cycle des pistons métalliques, qui à leur tour font tourner un vilebrequin attaché à certains engrenages qui entraînent certaines roues. Les roues ont des sacs en caoutchouc gonflés autour d'eux qui s'interfacent avec une surface en asphalte pour faire avancer une collection de sièges. Les sièges avancent parce que ...
Breton

130
J'ai lu tout cela et je ne sais toujours pas ce qu'est une monade, à part le fait que c'est quelque chose que les programmeurs Haskell ne comprennent pas assez bien pour expliquer. Les exemples n'aident pas beaucoup, étant donné que ce sont toutes des choses que l'on peut faire sans monades, et cette réponse ne montre pas clairement comment les monades les rendent plus faciles, mais plus confuses. La partie de cette réponse qui a failli être utile était où le sucre syntaxique de l'exemple # 2 a été supprimé. Je dis est venu près parce que, à part la première ligne, l'expansion ne ressemble pas vraiment à l'original.
Laurence Gonsalves

81
Un autre problème qui semble endémique aux explications des monades est qu'il est écrit en Haskell. Je ne dis pas que Haskell est un mauvais langage - je dis que c'est un mauvais langage pour expliquer les monades. Si je connaissais Haskell, je comprendrais déjà les monades, donc si vous voulez expliquer les monades, commencez par utiliser un langage que les gens qui ne connaissent pas les monades sont plus susceptibles de comprendre. Si vous devez utiliser Haskell, n'utilisez pas du tout le sucre syntaxique - utilisez le sous-ensemble le plus petit et le plus simple du langage que vous pouvez, et ne supposez pas une compréhension de Haskell IO.
Laurence Gonsalves

712

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.


13
J'apprécie votre réponse - en particulier la concession finale que tout cela est bien sûr possible aussi sans monades. Un point à souligner est que c'est surtout plus facile avec les monades, mais ce n'est souvent pas aussi efficace que de le faire sans elles. Une fois que vous devez impliquer des transformateurs, la superposition supplémentaire des appels de fonction (et des objets de fonction créés) a un coût difficile à voir et à contrôler, rendu invisible par une syntaxe intelligente.
SEH

1
À Haskell au moins, la plupart des frais généraux des monades sont supprimés par l'optimiseur. Le seul "coût" réel est donc la puissance cérébrale requise. (Ce n'est pas négligeable si la "maintenabilité" est quelque chose qui vous tient à cœur.) Mais généralement, les monades rendent les choses plus faciles , pas plus difficiles. (Sinon, pourquoi vous dérangeriez-vous?)
MathematicalOrchid

Je ne sais pas si Haskell le supporte ou non, mais mathématiquement, vous pouvez définir une monade en termes de >> = et retourner ou rejoindre et ap. >> = et return sont ce qui rend les monades pratiquement utiles, mais join et ap donnent une compréhension plus intuitive de ce qu'est une monade.
Jeremy List du

15
Venant d'un contexte de programmation non mathématique et non fonctionnel, cette réponse était la plus logique pour moi.
jrahhali

10
C'est la première réponse qui m'a en fait donné une idée de l'enfer qu'est une monade. Merci d'avoir trouvé un moyen de l'expliquer!
robotmay

186

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:

data Wrapped a = Wrap a

Pour envelopper des choses que nous définissons

return :: a -> Wrapped a
return x = Wrap x

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:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

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 :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindpeut faire un peu plus fmap, mais pas l'inverse. En fait, fmapne peut être défini qu'en termes de bindet return. Ainsi, lors de la définition d' une monade .. vous donner son type (ici , il a été Wrapped a), puis dire à quel point son returnet le bindtravail 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.


-> est une application de fonction de mise en miroir associative à droite, qui est associative à gauche, donc laisser les parenthèses ne fait aucune différence ici.
Matthias Benkard

1
Je ne pense pas que ce soit une très bonne explication. Les monades sont tout simplement un moyen? ok, de quelle façon? Pourquoi n'encapsulerais-je pas en utilisant une classe au lieu d'une monade?
Breton

4
@ mb21: Au cas où vous signaleriez simplement qu'il y a trop de crochets, notez que a-> b-> c n'est en fait que l'abréviation de a -> (b-> c). Écrire cet exemple particulier comme (a -> b) -> (Ta -> Tb) est à proprement parler simplement ajouter des caractères inutiles, mais c'est moralement "la bonne chose à faire" car il souligne que fmap mappe une fonction de type a -> b à une fonction de type Ta -> Tb. Et à l'origine, c'est ce que font les foncteurs dans la théorie des catégories et c'est de là que viennent les monades.
Nikolaj-K

1
Cette réponse est trompeuse. Certaines monades n'ont pas du tout de "wrapper", une telle fonction à partir d'une valeur fixe.

1
@DanMandel Monads sont des modèles de conception qui fournissent leur propre wrapper de type de données. Les monades sont conçues de manière à abstraire le code passe-partout. Ainsi, lorsque vous appelez une Monade dans votre code, cela fait des choses en coulisse dont vous ne voulez pas vous inquiéter. Pensez à Nullable <T> ou IEnumerable <T>, que font-ils dans les coulisses? C'est Monade.
sksallaj

168

Mais, vous auriez pu inventer des Monades!

sigfpe dit:

Mais tout cela présente les monades comme quelque chose d'ésotérique qui a besoin d'explication. Mais ce que je veux dire, c'est qu'ils ne sont pas du tout ésotériques. En fait, face à divers problèmes de programmation fonctionnelle, vous auriez été inexorablement amené à certaines solutions, qui sont toutes des exemples de monades. En fait, j'espère vous amener à les inventer maintenant si vous ne l'avez pas déjà fait. C'est alors un petit pas pour constater que toutes ces solutions sont en fait la même solution déguisée. Et après avoir lu ceci, vous pourriez être mieux placé pour comprendre d'autres documents sur les monades parce que vous reconnaîtrez tout ce que vous voyez comme quelque chose que vous avez déjà inventé.

Beaucoup de problèmes que les monades essaient de résoudre sont liés à la question des effets secondaires. Nous allons donc commencer par eux. (Notez que les monades vous permettent de faire plus que de gérer les effets secondaires, en particulier de nombreux types d'objets conteneurs peuvent être considérés comme des monades. Certaines des introductions aux monades ont du mal à concilier ces deux utilisations différentes des monades et à se concentrer sur une ou L'autre.)

Dans un langage de programmation impératif tel que C ++, les fonctions ne se comportent en rien comme les fonctions des mathématiques. Par exemple, supposons que nous ayons une fonction C ++ qui accepte un seul argument à virgule flottante et renvoie un résultat à virgule flottante. Superficiellement, cela peut ressembler un peu à une fonction mathématique mappant les réels aux réels, mais une fonction C ++ peut faire plus que simplement renvoyer un nombre qui dépend de ses arguments. Il peut lire et écrire les valeurs des variables globales ainsi qu'écrire la sortie à l'écran et recevoir des entrées de l'utilisateur. Dans un langage fonctionnel pur, cependant, une fonction ne peut lire que ce qui lui est fourni dans ses arguments et la seule façon dont elle peut avoir un effet sur le monde est à travers les valeurs qu'elle renvoie.


9
… Le meilleur moyen non seulement sur Internet, mais partout. (Le document original de Wadler, Monades pour la programmation fonctionnelle, que j'ai mentionné dans ma réponse ci-dessous est également bon.) Aucun des millions de tutoriels par analogie ne se rapproche.
ShreevatsaR

13
Cette traduction JavaScript du message de Sigfpe est la nouvelle meilleure façon d'apprendre les monades, pour les personnes qui ne connaissent pas déjà le Haskell avancé!
Sam Watkins

1
C'est ainsi que j'ai appris ce qu'est une monade. Accompagner le lecteur dans le processus d'invention d'un concept est souvent le meilleur moyen d'enseigner le concept.
Jordan

Cependant, une fonction acceptant l'objet écran comme argument et renvoyant sa copie avec du texte modifié serait pure.
Dmitri Zaitsev

87

Une monade est un type de données qui a deux opérations: >>=(aka bind) et return(aka unit). returnprend 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

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

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 >>=et returndevraient 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:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

[]et :sont les constructeurs de liste, ++est l'opérateur de concaténation, et Justet Nothingsont les Maybeconstructeurs. 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.


Qu'entendez-vous exactement par «mappe une fonction dessus»?
Casebash

Casebash, je suis délibérément informel dans l'introduction. Voir les exemples vers la fin pour avoir une idée de ce qu'implique le «mappage d'une fonction».
Chris Conway

3
Monad n'est pas un type de données. C'est une règle de composition des fonctions: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev a raison, les Monades fournissent en fait leur propre type de données, les Monades ne sont pas des types de données
sksallaj

78

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 Tpour laquelle il existe une fonction d'ordre supérieur, appelez-la map, qui transforme une fonction de type a -> b(étant donné deux types quelconques aet b) en une fonction T a -> T b. Cette mapfonction doit également obéir aux lois de l'identité et de la composition de sorte que les expressions suivantes renvoient true pour tous pet q(notation Haskell):

map id = id
map (p . q) = map p . map q

Par exemple, un constructeur de type appelé Listest un foncteur s'il est équipé d'une fonction de type (a -> b) -> List a -> List bqui obéit aux lois ci-dessus. La seule mise en œuvre pratique est évidente. La List a -> List bfonction 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 Tavec deux méthodes supplémentaires, join, de type T (T a) -> T a, et unit(parfois appelé return, forkou pure) de type a -> T a. Pour les listes en Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Pourquoi est-ce utile? Parce que vous pourriez, par exemple, mapsur une liste avec une fonction qui renvoie une liste. Joinprend la liste de listes résultante et les concatène. Listest une monade parce que c'est possible.

Vous pouvez écrire une fonction qui fait map, alors join. Cette fonction est appelée bind, ou flatMap, 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 joindoit être associative. Cela signifie que si vous avez une valeur xde type, [[[a]]]elle join (join x)doit être égale join (map join x). Et puredoit être une identité pour joinça join (pure x) == x.


3
léger ajout à la définition de «fonction d'ordre supérieur»: ils peuvent prendre des fonctions OU RETOUR. C'est pourquoi ils sont `` supérieurs '' car ils font des choses avec eux-mêmes.
Kevin a gagné le

9
Selon cette définition, l'addition est une fonction d'ordre supérieur. Il prend un nombre et renvoie une fonction qui ajoute ce nombre à un autre. Donc non, les fonctions d'ordre supérieur sont strictement des fonctions dont le domaine est constitué de fonctions.
Apocalisp

La vidéo « Brian Beckman: Don't fear the Monad » suit cette même logique.
icc97

48

[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:

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.

C'est exactement ça. L'idée va comme ceci:

  1. 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 Maybeou un IO.

  2. 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 Intpeut être un Just Intou Nothing. Maintenant, si vous ajoutez un Maybe Intà un Maybe Int, l'opérateur vérifiera pour voir s'ils sont tous les deux à l' Just Intintérieur, et si c'est le cas, déballera les Ints, leur passera l'opérateur d'addition, re-enveloppera le résultat Intdans un nouveau Just Int(qui est valide Maybe Int), et donc retourner a Maybe Int. Mais si l'un d'eux était un Nothingintérieur, cet opérateur reviendra immédiatement Nothing, ce qui est encore une fois valide Maybe Int. De cette façon, vous pouvez prétendre que vos Maybe Intchiffres ne sont que des nombres normaux et effectuer des calculs réguliers sur eux. Si vous deviez obtenir un Nothing, vos équations produiront toujours le bon résultat - sans que vous ayez à vérifier les déchets Nothingpartout .

Mais l'exemple est juste ce qui se passe pour Maybe. Si l'information supplémentaire était un IO, alors cet opérateur spécial défini pour IOs serait appelé à la place, et il pourrait faire quelque chose de totalement différent avant d'effectuer l'ajout. (OK, ajouter deux IO Ints ensemble est probablement absurde - je ne suis pas encore sûr.) (De plus, si vous avez prêté attention à l' Maybeexemple, 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.


12
Parfois, une explication d'un "apprenant" (comme vous) est plus pertinente pour un autre apprenant qu'une explication provenant d'un expert. Les apprenants pensent de la même façon :)
Adrian

Ce qui fait de quelque chose une monade, c'est l'existence d'une fonction de type M (M a) -> M a. Le fait que vous puissiez transformer cela en un type M a -> (a -> M b) -> M best ce qui les rend utiles.
Jeremy List

"monade" signifie à peu près "motif" ... non.
Merci

44

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:

  1. Pourquoi avez-vous besoin d'une monade?
  2. Qu'est-ce qu'une monade?
  3. Comment une monade est-elle implémentée?

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.

consolestate MyConsole = new consolestate;

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.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

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:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

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 un liftopé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.


Le séquençage n'est pas la seule raison de définir une monade. Une monade est n'importe quel foncteur qui a un lien et un retour. La liaison et le retour vous donnent le séquençage. Mais ils donnent aussi d'autres choses. Notez également que votre langage impératif préféré est en fait une monade IO fantaisie avec des classes OO. Simplifier la définition des monades signifie qu'il est facile d'utiliser le modèle d'interpréteur - définissez un dsl comme une monade et interprétez-le!
nomen


38

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 yla valeur peut devenir Mbtelle que, par exemple, (is_OK, b)si elle est yincluse 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 boolindiquant la présence d'un numéro valide et un numéro tuple tel que bool * 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 la boolpartie d'un tuple.

(Ma -> Mb) >> (Mb -> Mc)

Ici encore, la composition se produit naturellement avec composeet 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_OKest false. 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 la compositionplace de compose. En tant que tel, au lieu de simplement transférer des valeurs de la sortie d'une fonction à l'entrée d'une autre, Bindil inspecterait la Mpartie de Maet déciderait si et comment appliquer la fonction composée à la a. Bien sûr, la fonction bindserait définie spécifiquement pour notre particulier Mafin de pouvoir inspecter sa structure et effectuer tout type d'application que nous voulons. Néanmoins, le apeut être n'importe quoi car il bindpasse simplement l' ainspecteur à 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 lesMpartie de la structure d'entrée soit, en les simplifiant. Par conséquent...

(a -> Mb) >>= (b -> Mc) ou plus succinctement Mb >>= (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 contentmodè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 la bindfonction.

Par conséquent, une monade, c'est trois choses:

  1. un Mshell pour contenir les informations pertinentes de la monade,
  2. une bindfonction 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, et
  3. fonctions composables de la forme, a -> Mbproduisant 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 Mbstructure 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'est 0.

De plus, monads peut inclure des fonctions d'enveloppement qui encapsulent des valeurs, adans le type monadique Ma, et des fonctions générales a -> b, dans des fonctions monadiques a -> Mb, en encapsulant leurs résultats après application. Bien sûr, comme bind, ces fonctions de bouclage sont spécifiques à M. Un exemple:

let return a = [a]
let lift f a = return (f a)

La conception de la bindfonction 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é...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Alors...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitysignifie que bindpréserve l'ordre d'évaluation indépendamment du moment où il bindest appliqué. C'est-à-dire, dans la définition de Associativityci-dessus, forcer l'évaluation précoce de la parenthèse bindingde fet gn'aboutira qu'à une fonction qui attend Maafin de terminer le bind. Par conséquent, l'évaluation de Madoit être déterminée avant que sa valeur puisse être appliquée fet que le résultat à son tour soit appliqué à g.


"... mais j'espère que d'autres le trouveront utile" cela m'a en effet été utile, malgré toutes les phrases soulignées: D

C'est l'explication la plus concise et la plus claire des monades que j'ai jamais lues / regardées / entendues. Je vous remercie!
James

Il existe une différence importante entre Monad et Monoid. La monade est une règle pour "composer" des fonctions entre différents types, donc elles ne forment pas une opération binaire comme requis pour les monoïdes, voir ici pour plus de détails: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Oui. Vous avez raison. Votre article était au-dessus de ma tête :). Cependant, j'ai trouvé ce traitement très utile (et je l'ai ajouté au mien en tant qu'orientation pour les autres). Merci de votre attention
George

2
Vous avez peut-être confondu la théorie des groupes algébriques avec la théorie des catégories d' où vient Monade. La première est la théorie des groupes algébriques, qui n'est pas liée.
Dmitri Zaitsev

37

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:

  1. Les monades ont des restrictions sur ce qu'elles peuvent faire (voir la définition pour plus de détails).
  2. Ces restrictions, ainsi que le fait qu'il y a trois opérations impliquées, sont conformes à la structure de ce qu'on appelle une monade dans la théorie des catégories, qui est une branche obscure des mathématiques.
  3. Ils ont été conçus par les partisans des langages fonctionnels «purs»
  4. Les partisans des langages fonctionnels purs comme les branches obscures des mathématiques
  5. Parce que les mathématiques sont obscures et que les monades sont associées à des styles de programmation particuliers, les gens ont tendance à utiliser le mot monade comme une sorte de poignée de main secrète. Pour cette raison, personne n'a pris la peine d'investir dans un meilleur nom.

1
Les monades n'étaient pas «conçues», elles étaient appliquées d'un domaine (théorie des catégories) à un autre (E / S dans des langages de programmation purement fonctionnels). Newton a-t-il «conçu» le calcul?
Jared Updike

1
Les points 1 et 2 ci-dessus sont corrects et utiles. Les points 4 et 5 sont en quelque sorte ad hominem, même s'ils sont plus ou moins vrais. Ils n'aident pas vraiment à expliquer les monades.
Jared Updike

13
Re: 4, 5: La chose "Poignée de main secrète" est un hareng rouge. La programmation est pleine de jargon. Haskell arrive à appeler des choses ce qu'elles sont sans prétendre redécouvrir quelque chose. S'il existe déjà en mathématiques, pourquoi lui donner un nouveau nom? Le nom n'est vraiment pas la raison pour laquelle les gens n'obtiennent pas de monades; c'est un concept subtil. La personne moyenne comprend probablement l'addition et la multiplication, pourquoi ne comprend-elle pas le concept d'un groupe abélien? Parce que c'est plus abstrait et général et que cette personne n'a pas fait le travail pour envelopper la tête autour du concept. Un changement de nom n'aiderait pas.
Jared Updike

16
Soupir ... Je ne fais pas d'attaque contre Haskell ... Je faisais une blague. Donc, je ne comprends pas vraiment le fait d'être "ad hominem". Oui, le calcul a été "conçu". C'est pourquoi, par exemple, les étudiants en calcul sont enseignés la notation Leibniz, plutôt que les trucs bizarres utilisés par Netwton. Un meilleur design. Les bons noms aident beaucoup à comprendre. Si j'ai appelé les groupes abéliens «cosses de rides distendues», vous pourriez avoir du mal à me comprendre. Vous dites peut-être "mais ce nom n'a pas de sens", personne ne les appellera jamais ainsi. Pour les personnes qui n'ont jamais entendu parler de la théorie des catégories, la "monade" sonne comme un non-sens.
Scott Wisniewski

4
@Scott: désolé si mes nombreux commentaires donnaient l'impression que je devenais défensif à propos de Haskell. J'apprécie votre humour à propos de la poignée de main secrète et vous remarquerez que j'ai dit que c'était plus ou moins vrai. :-) Si vous appeliez les groupes abéliens "cosses de rides distendues", vous commettriez la même erreur en essayant de donner un meilleur nom aux monades (cf. F # "expressions de calcul"): le terme existe et les gens qui se soucient savent quelles monades sont, mais pas ce que sont les "choses floues chaudes" (ou "expressions de calcul"). Si je comprends bien votre utilisation du terme "opérateur de type", il y a beaucoup d'autres opérateurs de type que les monades.
Jared Updike

35

(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 :

Donc, les choses que je déteste chez Haskell:

Commençons par l'évidence. Tutoriels Monad. Non, pas des monades. Plus précisément les tutoriels. Ils sont sans fin, exagérés et mon dieu sont-ils fastidieux. De plus, je n'ai jamais vu de preuves convaincantes qu'ils aident réellement. Lisez la définition de classe, écrivez du code, surmontez le nom effrayant.

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.


2
Le seul problème avec le papier de Wadler est que la notation est différente, mais je conviens que le papier est assez convaincant et une motivation claire et concise pour appliquer des monades.
Jared Updike

+1 pour le "sophisme du tutoriel monade". Les didacticiels sur les monades s'apparentent à plusieurs didacticiels tentant d'expliquer le concept des nombres entiers. Un didacticiel dirait: "1 est similaire à une pomme"; un autre tutoriel dit, "2 est comme une poire"; un troisième dit: "3 est fondamentalement une orange". Mais vous n'obtenez jamais l'image complète d'un seul didacticiel. Ce que je retiens de cela, c'est que les monades sont un concept abstrait qui peut être utilisé à de nombreuses fins très différentes.
stakx - ne contribue plus le

@stakx: Oui, c'est vrai. Mais je ne voulais pas dire que les monades sont une abstraction que vous ne pouvez pas apprendre ou ne devriez pas apprendre; seulement qu'il est préférable de l'apprendre après avoir vu suffisamment d'exemples concrets pour percevoir une seule abstraction sous-jacente. Voir mon autre réponse ici .
ShreevatsaR

5
Parfois, je pense qu'il y a tellement de tutoriels qui tentent de convaincre le lecteur que les monades sont utiles en utilisant du code qui fait des choses compliquées ou utiles. Cela a nui à ma compréhension pendant des mois. Je n'apprends pas de cette façon. Je préfère voir du code extrêmement simple, faire quelque chose de stupide que je peux traverser mentalement et je n'ai pas pu trouver ce genre d'exemple. Je ne peux pas savoir si le premier exemple est une monade pour analyser une grammaire compliquée. Je peux savoir si c'est une monade pour additionner des entiers.
Rafael S. Calsaverini,

Mentionner uniquement le constructeur de type est incomplet: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

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":

if( clause ) then block

à son plus simple a deux emplacements - une clause et un bloc. La ifmonade 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.


14

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.


9

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.


9

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.


Les exemples de python l'ont rendu facile à comprendre! Merci d'avoir partagé.
Ryan Efendy

8

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.


Si vous utilisez jQuery, cette explication peut être très utile, surtout si votre Haskell n'est pas fort
byteclub

10
JQuery n'est absolument pas une monade. L'article lié est incorrect.
Tony Morris

1
Être "catégorique" n'est pas très convaincant. Pour une discussion utile sur le sujet, voir Is jQuery a monad - Stack Overflow
nealmcb

1
Voir aussi Google Douglas Crackford Parlez Monades et gonades et son code Javascript pour faire modads, en développant le comportement similaire des bibliothèques AJAX et promesses: Douglas Crockford / monade · GitHub
nealmcb


7

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 dataet les données circulent à travers les tuyaux.


1
Cela ressemble plus à Applicative qu'à Monad. Avec Monads, vous devez obtenir des données des tuyaux avant de pouvoir choisir le prochain tuyau à connecter.
Peaker

oui, vous décrivez l'Applicatif, pas la Monade. Monad est en train de construire le segment de tuyau suivant sur place, en fonction des données qui ont atteint ce point, à l'intérieur du tuyau.
Will Ness

6

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).



5

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.


5

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.


5

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é.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

Par exemple

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

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 mapet flatten. 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-category


5

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(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fprend 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 avec g.

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:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Vous n'aimez pas le fait fet gê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, fet gdoit 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:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Mais regardez ce qui se passe lorsque vous les composez:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

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:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Lire feed(f, m)comme « l' alimentation men f». Pour alimenter une paire <x, messages>en fonction fest de passer x dans f, se <y, message>sortir de fet retour <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <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:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Cela revient à passer la valeur à la fonction.

Deuxièmement: si vous introduisez une paire dans wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Cela ne change pas la paire.

Troisièmement: si vous définissez une fonction qui prend xet se nourrit g(x)dans f:

h(x) := feed(f, g(x))

et introduisez-y une paire:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Cela revient à alimenter la paire get à alimenter la paire résultante f.

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 valeur x. Si xest de type t, alors votre paire est une valeur de type "paire de tet chaîne". Appelez ce type M t.

Mest un constructeur de type: Mseul ne fait pas référence à un type, mais M _fait référence à un type une fois que vous avez rempli le blanc avec un type. An M intest une paire d'int et de chaîne. An M stringest 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.
  • feedprend une (fonction qui prend un tet retourne un M u) et un M tet retourne un M u.
  • wrapprend un vet retourne un M v.

t,, uet vsont 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é tdans une fonction équivaut à passer le déroulé tdans la fonction.

    Officiellement: feed(f, wrap(x)) = f(x)

  • Nourrir un M tdans wrapne fait rien pour le M t.

    Officiellement: feed(wrap, m) = m

  • Alimenter un M t(appelez-le m) dans une fonction qui

    • passe tdansg
    • obtient un M u(appelez-le n) deg
    • flux nenf

    est le même que

    • nourrir mdansg
    • obtenir ndeg
    • nourrir ndansf

    Formellement: feed(h, m) = feed(f, feed(g, m))h(x) := feed(f, g(x))

En règle générale, feedest appelé bind(AKA >>=dans Haskell) et wrapest appelé return.


5

Je vais essayer d'expliquer Monaddans 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 -> Stringet f :: String -> Bool.

Nous pouvons faire (f . g) x, ce qui est exactement la même chose que f (g x), où xest une Intvaleur.

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 gdoit être le même que le type accepté par f.

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 Inttype représente une Intvaleur qui peut ne pas être là, le IO Stringtype représente une Stringvaleur qui est là suite à l'exécution de certains effets secondaires.)

Disons que nous avons maintenant g1 :: Int -> Maybe Stringet f1 :: String -> Maybe Bool. g1et f1sont très similaires à get frespectivement.

Nous ne pouvons pas faire (f1 . g1) xou f1 (g1 x), où xest une Intvaleur. Le type du résultat renvoyé par g1n'est pas celui f1attendu.

Nous pourrions composer fet gavec l' .opérateur, mais maintenant nous ne pouvons pas composer f1et g1avec .. 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 g1et f1, de sorte que nous puissions écrire (f1 OPERATOR g1) x? g1renvoie 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 xest une Maybe Intvaleur. L' >>=opérateur aide à retirer cette Intvaleur du contexte «peut-être pas là» et à l'appliquer f1. Le résultat de f1, qui est un Maybe Bool, sera le résultat de toute l' >>=opération.

Et enfin, pourquoi est-ce Monadutile? Parce que Monadc'est la classe de type qui définit l' >>=opérateur, très similaire à la Eqclasse de type qui définit les opérateurs ==et /=.

Pour conclure, la Monadclasse 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 Monadpermet la composition de fonctions qui implique des valeurs dans des contextes .



IOW, Monad est un protocole d'appel de fonction généralisé.
Will Ness

Votre réponse est la plus utile à mon avis. Bien que je doive dire que je pense que l'accent doit être mis sur le fait que les fonctions auxquelles vous vous référez n'impliquent pas seulement des valeurs dans des contextes, elles mettent activement des valeurs dans des contextes. Ainsi, par exemple, une fonction, f :: ma -> mb se composerait très facilement avec une autre fonction, g :: mb -> m c. Mais les monades (lier spécifiquement) nous permettent de composer perpétuellement des fonctions qui mettent leur entrée dans le même contexte, sans que nous ayons besoin de retirer la valeur de ce contexte en premier (ce qui supprimerait effectivement les informations de la valeur)
James

@James Je pense que cela devrait être l'accent pour les foncteurs?
Jonas

@Jonas Je suppose que je n'ai pas bien expliqué. Quand je dis que les fonctions mettent des valeurs dans des contextes, je veux dire qu'elles ont du type (a -> mb). Celles-ci sont très utiles car mettre une valeur dans un contexte ajoute de nouvelles informations, mais il serait généralement difficile de chaîner un (a -> mb) et un (b -> mc) ensemble car nous ne pouvons pas simplement retirer la valeur du contexte. Il nous faudrait donc utiliser un processus compliqué pour enchaîner ces fonctions de manière sensible en fonction du contexte spécifique et les monades nous permettent simplement de le faire de manière cohérente, quel que soit le contexte.
James

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prologue

L'opérateur d'application $des fonctions

forall a b. a -> b

est défini canoniquement

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

en termes d'application de la fonction primitive de Haskell f x( infixl 10).

Composition .est définie en termes de $comme

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

et satisfait les équivalences forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.est associative, et idest 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 fde type de type * -> *avec une instance de la classe de type functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

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.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Les calculs de foncteurs ont le type

forall f t. Functor f => f t

Un calcul c rconsiste en des résultats r dans le contexte c .

Les fonctions monaires unaires ou les flèches de Kleisli ont le type

forall m a b. Functor m => a -> m b

Les flèches Kleisi sont des fonctions qui prennent un argument aet retournent un calcul monadique m b.

Les monades sont définies canoniquement en fonction du triple de Kleisli forall m. Functor m =>

(m, return, (=<<))

implémenté comme classe de type

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

L' identité Kleisli return est une flèche Kleisli qui promeut une valeur tdans un contexte monadique m. L'extension ou l' application Kleisli =<< applique une flèche Kleisli a -> m baux résultats d'un calcul m a.

La composition de Kleisli <=< est définie en termes d'extension comme

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< 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.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<est associative, et returnest son identité droite et gauche.

Identité

Le type d'identité

type Id t = t

est la fonction d'identité sur les types

Id :: * -> *

Interprété comme foncteur,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

Dans Haskell canonique, la monade d'identité est définie

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Option

Un type d'option

data Maybe t = Nothing | Just t

code un calcul Maybe tqui ne donne pas nécessairement un résultat t, un calcul qui peut «échouer». L'option monade est définie

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bn'est appliqué à un résultat que s'il Maybe adonne un résultat.

newtype Nat = Nat Int

Les nombres naturels peuvent être codés comme ces entiers supérieurs ou égaux à zéro.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Les nombres naturels ne sont pas fermés par soustraction.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

L'option monad couvre une forme de base de gestion des exceptions.

(-? 20) <=< toNat :: Int -> Maybe Nat

liste

La liste monad, sur le type de liste

data [] t = [] | t : [t]

infixr 5 :

et son fonctionnement monoïde additif "append"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

code un calcul non linéaire[t] produisant une quantité naturelle 0, 1, ...de résultats t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

L'extension =<<concatène ++toutes les listes [b]résultant des applications f xd'une flèche de Kleisli a -> [b]aux éléments de [a]dans une seule liste de résultats [b].

Que les diviseurs propres d'un entier positif nsoient

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

puis

forall n.  let { f = f <=< divisors } in f n   =   []

Pour définir la classe de type monade, au lieu de l'extension =<<, la norme Haskell utilise son flip, l' opérateur de liaison>>= .

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Par souci de simplicité, cette explication utilise la hiérarchie des classes de types

class              Functor f
class Functor m => Monad m

Dans Haskell, la hiérarchie standard actuelle est

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

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

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

se traduit approximativement par le bloc do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

la compréhension équivalente de la monade ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

et l'expression

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

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.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

La fonction de garde est définie

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

où le type d'unité ou «tuple vide»

data () = ()

Les monades additives qui prennent en charge le choix et l' échec peuvent être abstraites à l'aide d'une classe de type

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

failet <|>former un monoïdeforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

et failest l'élément zéro absorbant / annihilant des monades additives

_ =<< fail  =  fail

Si dans

guard (even p) >> return p

even pest vrai, alors la garde produit [()], et, par la définition de >>, la fonction constante locale

\ _ -> return p

est appliqué au résultat (). Si faux, le garde produit la liste monad fail( []), qui ne donne aucun résultat pour qu'une flèche de Kleisli soit appliquée >>, donc cela pest ignoré.

Etat

Malheureusement, les monades sont utilisées pour coder le calcul avec état.

Un processeur d'état est une fonction

forall st t. st -> (t, st)

qui transite un état stet donne un résultat t. L' État st peut être n'importe quoi. Rien, drapeau, compte, tableau, poignée, machine, monde.

Le type de processeur d'État est généralement appelé

type State st t = st -> (t, st)

La monade du processeur d'état est le * -> *foncteur du genre State st. Les flèches de Kleisli de la monade du processeur d'état sont des fonctions

forall st a b. a -> (State st) b

Dans Haskell canonique, la version paresseuse de la monade de processeur d'état est définie

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Un processeur d'état est exécuté en fournissant un état initial:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

L'accès à l'État est fourni par des primitives getet des putméthodes d'abstraction sur des monades avec état :

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdéclare une dépendance fonctionnelle du type d'état stsur la monade m; qu'un State t, par exemple, déterminera le type d'état de manière tunique.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

avec le type d'unité utilisé de manière analogue à voiden C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets est souvent utilisé avec les accesseurs de champ d'enregistrement.

L'équivalent monade d'état du thread variable

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

s0 :: Int, est le tout aussi référentiellement transparent, mais infiniment plus élégant et pratique

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)est un calcul de type State Int (), sauf pour son effet équivalent à return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

La loi monade de l'associativité peut être écrite en termes de >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

ou

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

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

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Entrée sortie

data World

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:

type IO t = World -> (t, World)

L'interaction est facilitée par des primitives impures

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

L'impureté du code qui utilise des IOprimitives est protocoleée en permanence par le système de type. Parce que la pureté est géniale, ce qui se passe en IOreste IO.

unsafePerformIO :: IO t -> t

Ou, du moins, devrait.

La signature de type d'un programme Haskell

main :: IO ()
main = putStrLn "Hello, World!"

s'étend à

World -> ((), World)

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 Test un mappage d'une catégorie Cà une catégorie D; pour chaque objet dans Cun objetD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

et pour chaque morphisme dans Cun morphismeD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

X, Ysont les objets C. HomC(X, Y)est la classe d'homomorphisme de tous les morphismes X -> Yen C. Le foncteur doit préserver l'identité et la composition du morphisme, la «structure» de C, en D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

La catégorie Kleisli d'une catégorie Cest donnée par un triple Kleisli

<T, eta, _*>

d'un endofuncteur

T : C -> C

( f), un morphisme d'identité eta( return) et un opérateur d'extension *( =<<).

Chaque morphisme de Kleisli dans Hask

      f :  X -> T(Y)
      f :: a -> m b

par l'opérateur d'extension

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

reçoit un morphisme dans Haskla catégorie de Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

La composition dans la catégorie Kleisli .Test donnée en termes d'extension

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

et satisfait les axiomes de catégorie

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

qui, en appliquant les transformations d'équivalence

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

en termes d'extension sont donnés canoniquement

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Les monades peuvent également être définies en termes non pas d'extension kleislienne, mais d'une transformation naturelle mu, appelée programmation join. Une monade est définie en termes de mutriple sur une catégorie C, d'un endofoncteur

     T :  C -> C
     f :: * -> *

et deux transformations naturelles

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

satisfaire les équivalences

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

La classe de type monade est alors définie

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

L' muimplémentation canonique de l'option monade:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

La concatfonction

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

est la joinmonade de la liste.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Les implémentations de joinpeuvent être traduites à partir du formulaire d'extension en utilisant l'équivalence

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

La traduction inverse de muvers extension est donnée par

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Mais pourquoi une théorie aussi abstraite devrait-elle être utile à la programmation?

La réponse est simple: en tant qu'informaticiens, nous apprécions l'abstraction ! Lorsque nous concevons l'interface avec un composant logiciel, nous voulons qu'elle révèle le moins possible sur l'implémentation. Nous voulons pouvoir remplacer l'implémentation par de nombreuses alternatives, de nombreuses autres «instances» du même «concept». Lorsque nous concevons une interface générique pour de nombreuses bibliothèques de programmes, il est encore plus important que l'interface que nous choisissons ait une variété d'implémentations. C'est la généralité du concept de monade que nous apprécions tellement, c'est parce que la théorie des catégories est si abstraite que ses concepts sont si utiles pour la programmation.

Il n'est donc guère surprenant que la généralisation des monades que nous présentons ci-dessous soit également étroitement liée à la théorie des catégories. Mais nous soulignons que notre objectif est très pratique: il ne s'agit pas de «mettre en œuvre la théorie des catégories», c'est de trouver un moyen plus général de structurer les bibliothèques combinatoires. C'est simplement notre chance que les mathématiciens aient déjà fait une grande partie du travail pour nous!

de la généralisation des monades aux flèches par John Hughes


4

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.

Triangle de Sierpinski

Ce qui précède est une fractale appelée triangle de Sierpinski, la seule fractale dont je me souvienne de dessiner. Les fractales sont une structure auto-similaire comme le triangle ci-dessus, dans lequel les parties sont similaires à l'ensemble (dans ce cas exactement la moitié de l'échelle comme triangle parent).

Les monades sont des fractales. Étant donné une structure de données monadique, ses valeurs peuvent être composées pour former une autre valeur de la structure de données. C'est pourquoi il est utile pour la programmation, et c'est pourquoi il se produit dans de nombreuses situations.


3
Vous voulez dire "ce dont le monde n'a pas besoin ..."? Belle analogie cependant!
groverboy

@ icc97 vous avez raison - le sens est assez clair. Sarcasme involontaire, excuses à l'auteur.
groverboy

Ce dont le monde a besoin, c'est d'un autre fil de commentaires confirmant un sarcasme, mais s'il est lu attentivement, j'ai écrit mais cela devrait être clair.
Eugene Yokota


4

Laissez le " {| a |m}" ci-dessous représenter un morceau de données monadiques. Un type de données qui annonce un a:

        (I got an a!)
          /        
    {| a |m}

Fonction,, fsait créer une monade, si seulement elle avait un a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Ici, nous voyons la fonction f,, essaie d'évaluer une monade mais est réprimandée.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion,, ftrouve un moyen d'extraire le aen utilisant >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Peu fsait, la monade et >>=sont en collusion.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

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

 data Maybe a = Nothing | Just a

a une instance de monade qui agira comme suit ...

Où, si l'affaire est Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Mais pour le cas de Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Ainsi, la monade Maybe permet à un calcul de continuer s'il contient réellement ce aqu'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 de f. 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:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

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é fen combinant les multiples sorties. J'inclus cet exemple pour montrer que s'il >>=est responsable de l'extraction a, il a également accès à la sortie liée finale de f. En effet, il n'en extraira jamais à amoins 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 IOmonade n'en a pas a, mais elle connaît un mec et elle en aura apour vous. La State stmonade a une cachette secrète stqu'elle passera fsous la table, même si fje viens juste de demander un a. La Reader rmonade est similaire à State st, bien qu'elle ne laisse que fregarder r.

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'autonomie f. C'est-à-dire, dans le cas ci-dessus Nothingpar exemple, fne peut plus décider quoi faire dans le cas de Nothing; il est encodé >>=. C'est le compromis. S'il était nécessaire fde décider quoi faire dans le cas de Nothing, alors cela faurait dû être une fonction de Maybe 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.


3

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.


3
Si je comprends bien, les monades sont plus que cela. L'encapsulation d'un état mutable dans des langages fonctionnels «purs» n'est qu'une application des monades.
thSoft
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.