1546 votes

Qu'est-ce qu'un monade ?

Après avoir brièvement examiné Haskell récemment, quelle serait une explication brève, succincte, pratique de ce qu'est essentiellement un monade ?

J'ai trouvé que la plupart des explications que j'ai rencontrées étaient assez peu accessibles et manquaient de détails pratiques.

12 votes

Eric Lippert a écrit une réponse à ces questions (stackoverflow.com/questions/2704652/…), qui est due à certaines questions vivent sur une page séparée.

72 votes

Voici une nouvelle introduction utilisant du javascript - Je l'ai trouvé très lisible.

7 votes

1167voto

JacquesB Points 19878

Première: Le terme monade est un peu vide de sens si vous n'êtes pas un mathématicien. Un autre terme est le calcul du générateur qui est un peu plus descriptif de ce qu'ils sont réellement utiles.

Vous demandez des exemples pratiques:

Exemple 1: compréhension de Liste:

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

Cette expression renvoie le double de tous les nombres impairs dans la gamme de 1 à 10. Très utile!

Il s'avère que c'est vraiment juste sucre syntaxique pour certaines opérations au sein de la Liste monade. La même compréhension de liste peut être écrite comme:

do
   x <- [1..10]
   if odd x 
       then [x * 2] 
       else []

Ou encore:

[1..10] >>= (\x -> if odd x then [x*2] else [])

Exemple 2: Entrée/Sortie:

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

Les deux exemples utilise des monades, aka le calcul des constructeurs. Le thème commun est que la monade des chaînes d'opérations dans certains spécifiques, de façon utile. Dans la compréhension de liste, les opérations sont enchaînés de telle sorte que si une opération retourne une liste, puis les opérations suivantes sont effectuées sur chaque élément dans la liste. Le IO monade sur l'autre main effectue les opérations de façon séquentielle, mais passe une "variable cachée", qui représente "l'état du monde", ce qui nous permet d'écrire IO code dans un pur manière fonctionnelle.

Il s'avère que le modèle de chaînage des opérations est très utile, et est utilisé pour beaucoup de choses différentes en Haskell.

Un autre exemple est des exceptions: à l'Aide de l' Error monade, les opérations sont enchaînés, telles qu'elles sont effectuées de manière séquentielle, sauf si une erreur est levée, auquel cas le reste de la chaîne est à l'abandon.

À la fois la liste-de la compréhension de la syntaxe et de la notation sont sucre syntaxique pour le chaînage des opérations à l'aide de l' >>= de l'opérateur. Une monade est fondamentalement juste un type qui prend en charge l' >>= de l'opérateur.

Exemple 3: Un analyseur

C'est très simple analyseur qui analyse une chaîne ou 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 d' char, digit etc. sont assez simples, ils correspondent ou ne correspondent pas. La magie est la monade qui gère le contrôle de flux: Les opérations sont effectuées de manière séquentielle jusqu'à une correspondance échoue, auquel cas la monade revient à la dernière <|> et tente l'option suivante. De nouveau, une sorte de chaînage des opérations avec certains autres, utiles à la sémantique.

Exemple 4: la programmation Asynchrone

Les exemples ci-dessus sont en Haskell, mais il s'avère F# prend également en charge les monades. Cet exemple est volé de 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 extrait d'une page web. Le punch line est l'utilisation de l' GetResponseAsync - il réellement attend la réponse sur un autre fil, tandis que le thread principal renvoie à partir de la fonction. Les trois dernières lignes sont exécutées sur le pondu fil lorsque la réponse n'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. L' async monade est capable de "diviser" le bloc sur son propre et surseoir à l'exécution de la seconde moitié. ( async {} De la syntaxe indique que le flux de contrôle dans le bloc est défini par l' async monade)

Comment ils fonctionnent

Alors, comment pouvez-monade faire toutes ces fantaisie de contrôle des flux de chose? Ce qui se passe réellement dans un bloc (ou un calcul de l'expression , comme on les appelle en F#), c'est que chaque opération (en gros à chaque ligne) est enveloppé dans une autre fonction anonyme. Ces fonctions sont ensuite combinées à l'aide de l' bind opérateur (orthographié >>= en Haskell). Depuis l' bind opération combine les fonctions, il peut les exécuter comme il l'entend: de manière séquentielle, à plusieurs reprises, dans le sens inverse, ignorer certains, exécuter certaines sur un thread séparé quand il se sent comme il et ainsi de suite.

Comme un exemple, c'est la version étendue de l'OI-code de l'exemple 2:

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

C'est la plus laide, mais c'est aussi plus évident que ce qui se passe réellement. L' >>= de l'opérateur est l'ingrédient magique: Elle prend une valeur (sur la gauche) et les combine avec une fonction (sur le côté droit), pour produire une nouvelle valeur. Cette nouvelle valeur est alors prise par le prochain >>= de l'opérateur et de nouveau combiné avec une fonction pour produire une nouvelle valeur. >>= peut être considéré comme un mini-évaluateur.

Notez que >>= est surchargé pour les différents types, de sorte que chaque monade a sa propre implémentation d' >>=. (Toutes les opérations de la chaîne doivent être du type de la même monade bien, sinon l' >>= opérateur ne fonctionnent pas.)

La plus simple possible la mise en œuvre de l' >>= seulement prend de la valeur sur la gauche et l'applique à la fonction sur la droite et renvoie le résultat, mais comme dit avant, ce qui rend l'ensemble du motif est utile quand il y a quelque chose de plus qui se passe dans la monade de mise en œuvre de l' >>=.

Il y a plus d'intelligence dans la façon dont les valeurs sont transmises d'une opération à l'autre, mais cela nécessite une explication plus approfondie de la Haskell type de système.

Résumé

En Haskell-conditions une monade est un type paramétré, qui est une instance de la Monade type de classe, qui définit >>= avec quelques autres opérateurs. En d'autres termes, une monade est juste un type pour lequel l' >>= y est défini.

En soi, >>= est juste lourd sorte d'enchaînement de fonctions, mais avec la présence de la notation qui cache la "plomberie", le monadique des opérations s'avère être une très belle et utile, abstraction, utiles à plusieurs endroits dans la langue, et utile pour créer votre propre mini-langues dans la langue.

Pourquoi sont les monades dur?

Pour de nombreux Haskell-apprenants, les monades sont un obstacle ils ont frappé comme un mur de briques. Ce n'est pas que les monades sont eux-mêmes complexes, mais que la mise en œuvre s'appuie sur de nombreuses autres avancées Haskell caractéristiques comme le type paramétré, les classes de type, et ainsi de suite. Le problème est que Haskell IO est basé sur des monades, et IO est probablement l'une des premières choses que vous voulez comprendre lors de l'apprentissage d'une nouvelle langue - après tout, ce n'est pas beaucoup de plaisir à créer des programmes qui ne produisent pas de sortie. Je n'ai pas de solution immédiate pour ce dilemme de la poule et de l'oeuf problème, sauf le traitement des IO comme "la magie qui se passe ici" jusqu'à ce que vous avez assez d'expérience avec d'autres parties de la langue. Désolé.

76 votes

En tant que quelqu'un qui a eu beaucoup de problèmes à comprendre les monades, je peux dire que cette réponse a aidé .. un peu. Cependant, il y a encore des choses que je ne comprends pas. De quelle manière la compréhension de la liste est-elle une monade? Y a-t-il une forme étendue de cet exemple? Une autre chose qui me dérange vraiment à propos de la plupart des explications sur les monades, y compris celle-ci - c'est qu'ils continuent de mélanger "qu'est-ce qu'une monade?" avec "à quoi sert une monade?" et "Comment une monade est-elle implémentée?". tu as sauté le requin quand tu as écrit "Une monade est essentiellement juste un type qui prend en charge l'opérateur >>=." Ce qui m'a juste...

11 votes

Je me gratte la tête. Cela semble être un détail d'implémentation, et cela ne m'aide pas vraiment à répondre à la question "Pourquoi devrais-je utiliser un monade". Cela peut être vrai, mais l'explication jusqu'à ce point ne m'a pas préparé à cela. Je ne pensais pas "Pourquoi c'est en effet un problème difficile, pourquoi, ce dont j'aurais besoin c'est un type qui supporte l'opérateur >>=. Oh hé, il s'avère que c'est ça une monade!" non, cela ne tournait pas dans ma tête, parce que je ne savais pas ce qu'était un opérateur >>=, ou à quoi cela servait, ni n'ai-je été présenté à un problème que cela résout.

4 votes

Vous avez bien sûr compensé plus tard, un peu, mais j'ai bien peur que je ne connaisse toujours pas la réponse à la question "Qu'est-ce qu'un monade", encore moins une explication simple et succincte.

757voto

MathematicalOrchid Points 15354

Expliquer "qu'est-ce qu'un monade" est un peu comme dire "qu'est-ce qu'un nombre?" Nous utilisons des nombres tout le temps. Mais imaginez si vous rencontriez quelqu'un qui ne savait rien des nombres. Comment diable expliqueriez-vous ce que sont les nombres? Et par où commenceriez-vous même pour décrire pourquoi cela pourrait être utile?

Qu'est-ce qu'une monade? La réponse courte: C'est une manière spécifique de chaîner des opérations ensemble.

En essence, vous écrivez des étapes d'exécution et les reliez avec la "fonction de liaison". (En Haskell, c'est nommé >>=.) Vous pouvez écrire les appels à l'opérateur de liaison vous-même, ou vous pouvez utiliser du sucre syntaxique qui fait insérer ces appels de fonction pour vous par le compilateur. Mais de toute façon, chaque étape est séparée par un appel à cette fonction de liaison.

Donc la fonction de liaison est comme un point-virgule; elle sépare les étapes dans un processus. Le rôle de la fonction de liaison est de prendre la sortie de l'étape précédente, et de l'envoyer à l'étape suivante.

Ça n'a pas l'air trop difficile, n'est-ce pas? Mais il y a plus d'un type de monade. 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" que la monade fait... en fait ce n'est pas très utile. Et c'est important de comprendre : Chaque monade utile fait quelque chose d'autre en plus d'être juste une monade. Chaque monade utile a un "pouvoir spécial", qui la rend unique.

(Une monade qui ne fait rien de spécial est appelée la "monade d'identité". Plutôt comme la fonction d'identité, cela semble être quelque chose complètement inutile, et pourtant cela s'avère ne pas l'être… Mais c'est une autre histoire™.)

Essentiellement, chaque monade a sa propre implémentation de la fonction de liaison. Et vous pouvez écrire une fonction de liaison de manière à effectuer des choses hoopy entre les étapes d'exécution. Par exemple :

  • Si chaque étape retourne un indicateur de succès/échec, vous pouvez avoir la fonction de liaison exécuter l'étape suivante seulement si la précédente a réussi. De cette façon, une étape échouante interrompt toute la séquence "automatiquement", sans aucun test conditionnel de votre part. (La Monade d'é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 ce soit une fonctionnalité du langage, vous pouvez définir comment elles fonctionnent. (Par exemple, peut-être que vous voulez ignorer les deux premières exceptions et n'interrompre que lorsqu'une troisième exception est déclenchée.)

  • Vous pouvez faire en sorte que chaque étape retourne plusieurs résultats, et avoir la fonction de liaison les parcourir en boucle, les transmettant chacun à l'étape suivante pour vous. De cette manière, vous n'avez pas à écrire des boucles partout quand vous avez affaire à plusieurs résultats. La fonction de liaison fait "automatiquement" tout cela pour vous. (La Monade de liste.)

  • En plus de passer un "résultat" d'une étape à une autre, vous pouvez avoir la fonction de liaison transmettre des données supplémentaires également. Ces données ne sont plus visibles dans votre code source, mais vous pouvez toujours y accéder de n'importe où, sans avoir à les transmettre manuellement à chaque fonction. (La Monade de lecteur.)

  • 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 réellement les effectuer. (La Monade d'état et son cousin la Monade d'écrivain.)

  • Parce que vous ne faites qu'effectuer des mises à jour destructives simulées, vous pouvez facilement faire des choses qui seraient impossibles avec des mises à jour destructives réelles. 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 interrompus, vous permettant de mettre en pause votre programme, d'intervenir et de modifier des données internes d'état, puis de le reprendre.

  • Vous pouvez implémenter des "continuations" en tant que monade. Cela vous permet de faire exploser les cerveaux des gens !

Tout cela et plus encore est possible avec les monades. Bien sûr, tout cela est également parfaitement possible sans monades également. C'est juste bien plus facile d'utiliser des monades.

17 votes

Je vous remercie pour votre réponse—surtout pour la concession finale que tout cela est bien évidemment possible aussi sans les monades. Un point à souligner est que c'est surtout plus facile avec les monades, mais ce n'est pas toujours aussi efficace que de le faire sans elles. Une fois que vous avez besoin d'inclure des transformateurs, la superposition supplémentaire des appels de fonctions (et des objets de fonctions créés) a un coût difficile à voir et à contrôler, rendu invisible par une syntaxe astucieuse.

3 votes

En Haskell au moins, la plupart des surcharges des monades sont supprimées par l'optimiseur. Donc le seul "coût" réel est dans la puissance cérébrale requise. (Cela n'est pas du tout négligeable si "la maintenabilité" est quelque chose qui vous importe.) Mais en général, les monades rendent les choses plus faciles, pas plus difficiles. (Sinon, pourquoi vous embêteriez-vous ?)

0 votes

Je ne suis pas sûr que Haskell prend en charge cela, mais mathématiquement, vous pouvez définir un monade soit en termes de >>= et de return, soit de join et d'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 un monade.

201voto

Arnar Points 351

En réalité, contrairement à la compréhension commune des Monades, elles n'ont rien à voir avec l'état. Les Monades sont simplement un moyen d'encapsuler des éléments et de fournir des méthodes pour effectuer des opérations sur les éléments encapsulés sans les décapsuler.

Par exemple, vous pouvez créer un type pour envelopper un autre, en Haskell:

data Wrapped a = Wrap a

Pour encapsuler des éléments, nous définissons

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

Pour effectuer des opérations sans décapsuler, disons que vous avez une fonction f :: a -> b, alors vous pouvez faire ceci pour élever cette fonction pour agir sur des 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 faire cet élevant, qui est bind:

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

bind peut faire un peu plus que fmap, mais pas l'inverse. En fait, fmap peut être défini uniquement en termes de bind et de return. Ainsi, lors de la définition d'une monade... vous spécifiez son type (ici c'était Wrapped a) et ensuite vous indiquez comment ses opérations return et bind fonctionnent.

La chose intéressante est que cela se révèle être un schéma si général qu'il apparaît un peu partout, encapsuler l'état de manière pure n'est qu'une de ses manifestations.

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 de l'évaluation, comme c'est le cas dans la monade IO de Haskell, consultez IO Inside.

Quant à la compréhension des monades, ne vous inquiétez pas trop. Lisez ce qui vous intéresse à leur sujet et ne vous en faites pas si vous ne comprenez pas tout de suite. Ensuite, plonger directement dans un langage comme Haskell est la solution à privilégier. Les monades font partie de ces choses où la compréhension s'infiltre dans votre cerveau par la pratique, un jour vous vous rendez tout à coup compte que vous les comprenez.

0 votes

-> est associatif à droite, en miroir de l'application de fonction, qui est associatif à gauche, donc en laissant les parenthèses dehors ne fait pas de différence ici.

0 votes

Votre explication a fonctionné pour moi. J'aurais ajouté cependant une somme limitée de quelques monades standard (reader, state, maybe, ...) pour illustrer quelques utilisations pratiques et emballages.

3 votes

Je ne pense pas que ce soit une très bonne explication du tout. Les monades sont simplement UNE façon? D'accord, mais quelle façon? Pourquoi n'encapsulerais-je pas en utilisant une classe plutôt qu'une monade?

176voto

nlucaroni Points 21502

Mais, Vous auriez pu inventer les monades!

sigfpe dit:

Mais tous ceux-ci présentent les monades comme quelque chose d'ésotérique nécessitant une explication. Ce que je veux argumenter, c'est qu'elles ne sont pas ésotériques du tout. En fait, confronté à divers problèmes en programmation fonctionnelle, vous auriez été amené, inexorablement, à certaines solutions, toutes étant des exemples de monades. En fait, j'espère vous amener à les inventer maintenant si ce n'est pas déjà fait. C'est alors un petit pas pour remarquer que toutes ces solutions sont en fait une seule et même solution déguisée. Et après avoir lu ceci, vous serez peut-être en meilleure position pour comprendre d'autres documents sur les monades car vous reconnaîtrez tout ce que vous voyez comme quelque chose que vous avez déjà inventé.

De nombreux problèmes que cherchent à résoudre les monades sont liés à la question des effets secondaires. Nous commencerons donc 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 introductions aux monades ont du mal à concilier ces deux utilisations différentes des monades et se concentrent juste sur l'une ou l'autre.)

Dans un langage de programmation impératif comme C++, les fonctions ne se comportent en rien comme les fonctions des mathématiques. Par exemple, supposons que nous ayons une fonction C++ qui prend un argument de type flottant et renvoie un résultat de type flottant. Superficiellement, cela peut sembler un peu comme une fonction mathématique qui fait correspondre des réels à des réels, mais une fonction C++ peut faire plus que simplement retourner un nombre dépendant de ses arguments. Elle peut lire et écrire les valeurs des variables globales ainsi qu'écrire des sorties à l'écran et recevoir des entrées de l'utilisateur. En revanche, dans un langage fonctionnel pur, 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 retourne.

10 votes

…meilleure manière non seulement sur internet, mais n'importe où. (L'article original de Wadler Monads for functional programming que j'ai mentionné dans ma réponse ci-dessous est également bon.) Aucun des innombrables tutoriels par analogie ne s'en approche.

17 votes

Cette traduction en JavaScript du post de Sigfpe est la nouvelle meilleure manière d'apprendre les monades, pour les personnes qui ne maîtrisent pas encore le Haskell avancé!

1 votes

Voici comment j'ai appris ce qu'est un monade. Guider le lecteur à travers le processus d'invention d'un concept est souvent la meilleure façon d'enseigner le concept.

89voto

Chris Conway Points 24671

Un monade est un type de données qui possède deux opérations : >>= (alias bind) et return (alias unit). return prend une valeur arbitraire et crée une instance du monade avec celle-ci. >>= prend une instance du monade et applique une fonction dessus. (Vous pouvez déjà voir qu'un monade est un type de données étrange, puisque dans la plupart des langages de programmation vous ne pourriez pas écrire une fonction qui prend une valeur arbitraire et crée un type à partir de celle-ci. 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 vraiment important : les "lois" codifient simplement la manière dont des implémentations sensées des opérations devraient se comporter (fondamentalement, que >>= et return devraient être d'accord sur la façon dont les valeurs sont transformées en instances de monade et que >>= est associatif).

Les monades ne concernent pas seulement l'état et les E/S : elles abstraient un motif de calcul commun qui inclut le travail avec l'état, les E/S, les exceptions et la non-détermination. Probablement les monades les plus simples à comprendre sont les listes et les types d'option :

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 Just et Nothing sont les constructeurs de Maybe. Ces deux monades encapsulent des motifs courants et utiles de calcul sur leurs types de données respectifs (notez que ni l'un ni l'autre n'a à voir avec les effets secondaires ou les E/S).

Vous devez réellement expérimenter en écrivant du code Haskell non trivial pour apprécier ce que sont les monades et pourquoi elles sont utiles.

0 votes

Que veux-tu dire exactement par "mappe une fonction dessus" ?

0 votes

Casbah, je suis délibérément informel dans l'introduction. Consultez les exemples près de la fin pour vous faire une idée de ce que "mapper une fonction" implique.

3 votes

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X