Qu'est ce qu'une monade?

après avoir brièvement regardé Haskell récemment, qu'est-ce que serait une explication brève, succincte, pratique quant à ce qu'est essentiellement une monade?

j'ai trouvé que la plupart des explications que j'ai trouvées sont assez inaccessibles et manquent de détails pratiques.

1248
demandé sur Peter Mortensen 2008-09-05 03:26:44

30 réponses

D'abord: le terme monad est un peu vide si vous n'êtes pas un mathématicien. Un autre terme est calculateur qui est un peu plus descriptif de ce qu'ils sont réellement utiles pour.

vous demandez des exemples pratiques:

exemple 1: Liste de compréhension :

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

cette expression renvoie les doubles de all odd dans la plage de 1 à 10. Très utile!

il s'avère que c'est vraiment du sucre syntaxique pour certaines opérations dans la liste monad. La même Liste de compréhension peut être écrite comme:

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

ou même:

[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 utilisent des monades, alias computation builders. Le thème commun est que la monade "1519540920 de" chaînes " opérations , de façon utile. Dans la compréhension de la liste, les opérations sont enchaînées de sorte que si une opération renvoie une liste, alors les opérations suivantes sont effectuées sur chaque article dans la liste. D'un autre côté, le io monad exécute les opérations de manière séquentielle, mais passe à côté d'une "variable cachée", qui représente "l'état du monde", ce qui nous permet d'écrire du code d'entrée/sortie d'une manière purement fonctionnelle.

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

un autre exemple est celui des exceptions: à l'aide du monad Error , les opérations sont enchaînées de telle sorte qu'elles sont exécutées de manière séquentielle, sauf si une erreur est lancée, auquel cas le reste de la chaîne est abandonné.

la syntaxe de compréhension des listes et la notation sont toutes deux syntaxiques. pour les opérations de chaînage utilisant l'opérateur >>= . Un monad est fondamentalement juste un type qui soutient l'opérateur >>= .

exemple 3: a parser

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 char , digit , etc. sont assez simples. Ils correspondent ou ne correspondent pas. La magie est la monade qui gère le flux de contrôle: les opérations sont effectuées de manière séquentielle jusqu'à ce qu'une correspondance échoue, auquel cas le monad recule jusqu'à la dernière <|> et essaie l'option suivante. Encore une fois, une façon d'enchaîner les opérations avec quelques sémantiques supplémentaires et utiles.

exemple 4: programmation asynchrone

les exemples ci-dessus sont en Haskell, mais il s'avère que F# supporte aussi 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 récupère une page web. La ligne de pointage est l'utilisation de GetResponseAsync - il attend effectivement la réponse sur un thread séparé, tandis que le thread principal retourne 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 explicitement créer une fonction séparée pour les lignes que la poignée de la réponse. Le async monad est capable de" fendre " le bloc tout seul et de reporter l'exécution de la seconde moitié. (La syntaxe async {} indique que le flux de contrôle dans le bloc est défini par le monad async .)

Comment ils fonctionnent

alors comment une monade peut-elle faire tout ce truc fantaisiste de contrôle-Flux? Ce qui se passe réellement dans un do-block (ou une expression de calcul comme ils sont appelés en F#), est que chaque opération (essentiellement chaque ligne) est enveloppé dans une fonction anonyme séparée. Ces fonctions sont ensuite combinées en utilisant l'opérateur bind (orthographié >>= dans Haskell). Puisque l'opération bind combine des fonctions, elle peut les exécuter comme bon lui semble: séquentiellement, plusieurs fois, à l'envers, en jeter, en exécuter sur un thread séparé quand elle le souhaite et ainsi de suite.

comme exemple, c'est 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 alors prise par l'opérateur suivant >>= et de nouveau combinée avec une fonction pour produire une nouvelle valeur. >>= peut être considéré comme un mini-évaluateur.

notez que >>= est surchargé pour différents types, de sorte que chaque monade a sa propre mise en œuvre de >>= . (Toutes les opérations de la chaîne doivent être du même type, sinon l'opérateur >>= ne fonctionnera pas.)

la mise en œuvre la plus simple possible de >>= prend juste la valeur à gauche et l'applique à la fonction à droite et renvoie le résultat, mais comme dit auparavant, ce qui rend l'ensemble utile est quand il ya quelque chose de plus en cours dans la mise en œuvre de la monad de >>= .

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

Résumant

en Haskell-terms un monad est un type paramétré qui est une instance de la classe de type Monad, qui définit >>= avec quelques autres opérateurs. En termes simples, un monad est juste un type pour lequel l'opération >>= est définie.

En soi >>= est juste une lourde 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 la création de votre propre mini-langues dans la langue.

pourquoi les monades sont-elles dures?

pour beaucoup D'Haskell-apprenants, 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 D'Haskell comme les types paramétrés, les classes de type, et ainsi de suite. Le problème est que Haskell I/O est basé sur des monades, et I/O est probablement l'une des premières choses que vous voulez comprendre lors de l'apprentissage d'une nouvelle langue - après tout, il n'est pas très amusant de créer des programmes qui ne produisent pas de résultats. Je n'ai pas de solution immédiate pour ce problème de poule et d'œuf, sauf en traitant les e/s comme "la magie se produit ici" jusqu'à ce que vous ayez assez d'expérience avec d'autres parties de la langue. Désolé.

Excellent blog sur les monades: http://adit.io/posts/2013-04-17-functors les applications,_and_monads_in_pictures.html

976
répondu JacquesB 2017-04-05 14:33:53

Expliquant "qu'est ce qu'une monade" est un peu comme dire "qu'est ce qu'un nombre?"Nous utilisons des nombres tout le temps. Mais imaginez que vous ayez rencontré quelqu'un qui ne connaissait rien aux nombres. Comment le heck pourriez-vous expliquer ce que sont les nombres? Et comment décririez-vous pourquoi cela pourrait être utile?

qu'est Ce qu'une monade? La réponse courte: C'est une façon spécifique d'enchaîner les opérations ensemble.

en essence, vous écrivez les étapes d'exécution et leur liaison avec la "fonction bind". (À Haskell, il s'appelle >>= .) Vous pouvez écrire les appels à l'opérateur bind vous-même, ou vous pouvez utiliser le sucre de syntaxe qui rend le compilateur insérer ces appels de fonction pour vous. Mais dans tous les cas, chaque étape est séparée par un appel à cette fonction bind.

ainsi la fonction de liaison est comme un point-virgule; elle sépare les étapes d'un processus. La fonction de liaison de l'emploi est de prendre la sortie de la précédente étape, et l'alimenter dans la prochaine étape.

ça ne sonne pas trop dur, n'est-ce pas? Mais il y a plus d'un sorte de monade. Pourquoi? Comment?

Eh bien, la fonction de liaison peut juste prendre le résultat d'une étape, et l'alimenter à l'étape suivante. Mais si c'est "tous", la monade... ce n'est pas vraiment très utile. Et c'est important de comprendre: chaque utile monade fait quelque chose d'autre en plus de au simple fait d'être un monad. Chaque utile monade a un" pouvoir spécial", ce qui la rend unique.

(une monade qui fait rien spécial est appelé le"monade d'identité". Un peu comme la fonction d'identité, cela ressemble à une chose tout à fait inutile, mais il s'avère ne pas être... Mais c'est une autre histoire™.)

fondamentalement, chaque monade a sa propre mise en œuvre du bind fonction. Et vous pouvez écrire une fonction bind telle qu'elle fait des choses saugrenues entre les étapes d'exécution. Par exemple:

  • si chaque étape renvoie un indicateur de succès/échec, vous pouvez faire exécuter l'étape suivante par bind seulement si l'étape précédente a réussi. De cette façon, une étape ratée interrompt toute la séquence "automatiquement", sans aucun test conditionnel de votre part. (Le Échec De La Monade .)

  • en étendant cette idée, vous pouvez implémenter des "exceptions". (Le "1519340920 d'Erreur" Monade ou l'Exception " Monade .) Parce que vous les définissez vous-même plutôt que d'en faire une caractéristique linguistique, vous pouvez définir comment ils fonctionnent. (Par exemple, peut-être voulez-vous ignorer les deux premières exceptions et avorter seulement quand une troisième exception est lancée.)

  • Vous pouvez faire de chaque étape de retour résultats multiples , et avoir la boucle de la fonction bind sur eux, l'alimentation de chacun dans l'étape suivante pour vous. De cette façon, vous n'avez pas à continuer à écrire des boucles partout lorsqu'il s'agit de résultats multiples. La fonction de liaison "automatiquement" fait tout cela pour vous. (La Liste Monad .)

  • en plus de passer un "résultat" d'une étape à l'autre, vous pouvez avoir la fonction bind passer des données supplémentaires autour aussi. Ces données n'apparaissent plus dans votre code source, mais vous pouvez toujours y accéder de n'importe où, sans avoir à les passer manuellement à chaque fonction. (Le Lecteur Monade .)

  • Vous pouvez faire en sorte que les "données supplémentaires" peut être remplacé. Cela vous permet de simuler des mises à jour destructives , sans réellement faire des mises à jour destructives. (La Monade D'État et son cousin le écrivain Monad .)

  • parce que vous êtes seulement simulant mises à jour destructives, vous pouvez trivialement faire des choses qui seraient impossibles avec réel mises à jour destructives. Par exemple , vous pouvez annuler la dernière mise à jour , ou revenir à une version plus ancienne .

  • Vous pouvez faire un monad où les calculs peuvent être mis en pause , de sorte que vous pouvez mettre en pause votre programme, aller et bricoler avec les données de l'état interne, et 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 plus est possible avec des monades. Bien sûr, tout cela est aussi parfaitement possible sans monades aussi. C'est juste drastiquement plus facile en utilisant des monades.

646
répondu MathematicalOrchid 2017-04-19 20:50:58

en fait, contrairement à la compréhension commune des monades, ils n'ont rien à voir avec l'état. Les monades sont simplement un moyen d'emballer des choses et de fournir des méthodes pour faire des opérations sur les choses emballées sans les déballer.

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

data Wrapped a = Wrap a

pour envelopper les choses que nous définissons

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

pour effectuer des opérations sans déballage, fonction f :: a -> b , alors vous pouvez faire cela à lift cette fonction pour agir sur les valeurs enveloppées:

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

C'est à peu près tout ce qu'il y a à comprendre. Cependant, il s'avère qu'il y a une fonction plus générale pour faire ce lifting , 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 vice versa. En fait, fmap ne peut être défini que en termes de bind et return . Donc, en définissant une monade.. vous donnez son type (ici c'était Wrapped a ) et ensuite vous dites comment ses opérations return et bind fonctionnent.

la chose cool est que cela s'avère être un tel modèle général qu'il apparaît partout, encapsulant l'état d'une manière pure est seulement 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 donc ordre d'évaluation de contrôle, comme il est utilisé dans IO monad de Haskell, check out IO Inside .

quant à la compréhension des 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. Alors plonger dans une langue comme Haskell, c'est la solution. 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 comprendre.

167
répondu Arnar 2017-06-02 14:34:04

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 ésotérique. En fait, face à divers problèmes de programmation fonctionnelle, vous auriez été amenés, inexorablement, à certaines solutions, qui sont toutes des exemples de monades. En fait, j'espère pour que tu les inventes maintenant si tu ne l'as pas déjà fait. C'est alors un petit pas de remarquer que toutes ces solutions sont en fait la même solution déguisée. Et après avoir lu ceci, vous pourriez être dans une meilleure position pour comprendre d'autres documents sur monads parce que vous reconnaîtrez tout ce que vous voyez comme quelque chose que vous avez déjà inventé.

bon Nombre des problèmes que les monades essayer de résoudre sont liés à la question des effets secondaires. Donc nous allons commencer avec eux. (Notez que les monades vous permettent de faire plus que manipuler les effets secondaires, en particulier de nombreux types d'objet de conteneur peuvent être considérés comme des monades. Certaines des introductions aux monades trouvent difficile de concilier ces deux utilisations différentes des monades et de se concentrer sur l'une ou l'autre.)

dans un langage de programmation impératif tel que C++, les fonctions ne se comportent pas comme les fonctions des mathématiques. Par exemple, supposons que nous ayons une fonction C++ qui prend un seul argument flottant et renvoie un résultat en virgule flottante. Superficiellement, il semble un peu comme une fonction mathématique de la cartographie réels réels, mais une fonction C++ peuvent faire plus que simplement retourner un nombre qui dépend de ses arguments. Il peut lire et écrire les valeurs des variables globales ainsi que l'écriture de la sortie à l'écran et la réception de l'entrée de l'utilisateur. Dans un langage purement fonctionnel, cependant, une fonction ne peut lire ce qui est fourni dans ses arguments et la seule façon dont il peut avoir un effet sur le monde est par les valeurs qu'elle renvoie.

161
répondu nlucaroni 2015-03-31 05:35:32

un monad est un type de données qui a deux opérations: >>= (alias bind ) et return (alias unit ). return prend une valeur arbitraire et crée une instance de la monade. >>= prend une instance du monad et trace une fonction dessus. (Vous pouvez déjà voir qu'un monad est une sorte étrange de type de données, puisque dans la plupart des langages de programmation vous ne pouviez pas écrire une fonction qui prend une valeur arbitraire et en crée un type. Les monades utilisent un genre polymorphisme paramétrique .)

en notation Haskell, l'interface monad est écrite

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

ces opérations sont supposées obéir à certaines "lois", mais ce n'est pas d'une importance terrifiante: les "lois" codifient simplement la façon dont les implémentations sensées des opérations doivent se comporter (fondamentalement, que >>= et return doivent s'accorder sur la façon dont les valeurs se transforment dans les instances monad et que >>= est associatif).

monades ne sont pas seulement sur l'état et l'e/s: ils abstraient un modèle commun de calcul qui comprend le travail avec l'état, l'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 list, ++ est l'opérateur de concaténation, et Just et Nothing sont les constructeurs Maybe . 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 quoi que ce soit à voir avec les effets secondaires ou L'e/s).

vous devez vraiment jouer autour de l'écriture d'un code Haskell non-trivial pour apprécier ce que les monades sont au sujet et pourquoi ils sont utiles.

78
répondu Chris Conway 2015-08-31 13:44:06

Vous devez d'abord comprendre ce qu'est un foncteur. Avant cela, comprendre les fonctions d'ordre supérieur.

Un fonction d'ordre supérieur est simplement une fonction qui prend une fonction en tant qu'argument.

A functor est toute construction de type T pour laquelle il existe une fonction d'ordre supérieur, l'appeler map , qui transforme une fonction de type a -> b (compte tenu de deux types a et b ) en fonction T a -> T b . Cette fonction map doit aussi obéir aux lois de l'identité et de la composition telles que les expressions suivantes reviennent vraies pour toutes p et q (notation de Haskell):

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

par exemple, un constructeur de type appelé List est un foncteur s'il est équipé d'une fonction de type (a -> b) -> List a -> List b qui obéit aux lois ci-dessus. La seule application pratique est évidente. La fonction List a -> List b qui en résulte itère sur la liste donnée, appelant la fonction (a -> b) pour chaque élément, et renvoie la liste des résultats.

Un monade est essentiellement juste un foncteur T avec deux méthodes supplémentaires, join , de type T (T a) -> T a , et unit (parfois appelé return , fork , ou 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, map plus d'une liste avec une fonction qui renvoie une liste. Join prend la liste résultante des listes et les concaténate. List est une monade, parce que c'est possible.

vous pouvez écrire une fonction qui fait map , puis join . Cette fonction est appelée bind , ou flatMap , ou (>>=) , ou (=<<) . C'est normalement comment une instance monad est donnée dans Haskell.

Une monade doit satisfaire à certaines lois, à savoir que join doit être associative. Cela signifie que si vous avez une valeur x de type [[[a]]] alors join (join x) devrait être égal à join (map join x) . Et pure doit être une identité pour join telle que join (pure x) == x .

71
répondu Apocalisp 2018-06-26 13:38:22

[Disclaimer: je suis encore en train de complètement grok monades. Ce qui suit est exactement ce que j'ai compris jusqu'à présent. Si c'est mal, j'espère que quelqu'un de bien informé m'appellera sur le tapis.]

Arnar a écrit:

monades sont tout simplement un moyen d'emballer des choses et de fournir des méthodes pour faire des opérations sur la substance enveloppée sans le déballer.

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

  1. Vous prenez une sorte de valeur et l'envelopper avec quelques informations supplémentaires. Tout comme la valeur est d'une certaine sorte (par ex. un entier ou une chaîne de caractères), de sorte que les informations supplémentaires sont d'un certain type.

    par exemple, cette information supplémentaire pourrait être un Maybe ou un IO .

  2. alors vous avez quelques opérateurs qui vous permettent d'opérer sur les données enveloppées tandis que apportant des 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 enveloppée.

    par exemple, un Maybe Int peut être un Just Int ou Nothing . Maintenant, si vous ajoutez un Maybe Int à un Maybe Int , l'opérateur vérifiera pour voir s'ils sont tous les deux Just Int s à l'intérieur, et si c'est le cas, déballera le Int s, les passer l'opérateur d'addition, ré-envelopper le résultat Int dans un nouveau Just Int (qui est un valide Maybe Int ), et donc retourner un Maybe Int . Mais si l'un d'eux était un Nothing à l'intérieur, cet opérateur est juste retourner immédiatement Nothing , ce qui est encore valide Maybe Int . De cette façon, vous pouvez prétendre que vos Maybe Int sont juste des nombres normaux et effectuer des maths régulières sur eux. Si vous obtenez un Nothing , votre équations donneront toujours le bon résultat – sans que vous ayez à litière vérifie Nothing partout .

mais l'exemple est exactement ce qui se passe pour Maybe . Si l'information supplémentaire était un IO , alors cet opérateur spécial défini pour IO s serait appelé à la place, et il pourrait faire quelque chose de totalement différent avant d'effectuer l'addition. (OK, ajouter deux IO Int s ensemble est probablement absurde – Je ne suis pas encore sûr.) (Aussi, si vous avez tenu compte de l'exemple Maybe , vous avez remarqué que "l'habillage d'une valeur avec des trucs supplémentaires" n'est pas toujours correcte. Mais il est difficile d'être précis, correct et précis sans être impénétrable.)

en gros, "monad" signifie en gros "pattern" . Mais au lieu d'un livre plein de modèles expliqués de manière informelle et spécifiquement nommés, vous avez maintenant une construction de langue - syntaxe et tout – qui vous permet de déclarer de nouveaux modèles comme des choses dans votre programme . (L'imprécision ici est de tous les modèles doivent suivre une forme particulière, donc une monade n'est pas tout à fait comme générique comme 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. Se demander ce qui fait qu'une chose est une monade est tout aussi vague que se demander ce qui fait qu'une chose est un modèle.

mais pensez aux implications d'avoir un support syntaxique dans le langage pour l'idée d'un modèle: au lieu d'avoir à lire le Gang de quatre livre et de mémoriser la construction d'un modèle particulier, vous juste écrire code qui met en œuvre Ce modèle d'une manière agnostique, Générique une fois et puis vous êtes fait! Vous pouvez ensuite réutiliser ce modèle, comme Visiteur ou stratégie ou Façade ou autre, simplement en décorant les opérations de votre code avec, sans avoir à re-mettre en œuvre!

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, ça aussi bien sûr, teehee), mais en fait rend le code plus simple.

42
répondu Aristotle Pagaltzis 2011-08-28 05:03:25

Après beaucoup d'efforts, je pense que je comprends enfin la monade. Après avoir relu ma longue critique de la réponse qui a été votée par une majorité écrasante, je donnerai cette explication.

il y a trois questions auxquelles il faut répondre pour comprendre les monades:

  1. Pourquoi avez-vous besoin d'un monad?
  2. qu'est Ce qu'une monade?
  3. comment un monad est-il mis en œuvre?

Comme je l'ai fait remarquer dans mes commentaires initiaux, trop d'explications de la monad se retrouvent dans la question 3, sans, et avant qu'elle ne couvre vraiment adéquatement la question 2, ou la question 1.

Pourquoi avez-vous besoin d'une monade?

Pur fonctionnel langages comme Haskell sont différents langages comme C ou Java, un pur programme fonctionnel n'est pas nécessairement exécutées dans un ordre spécifique, une étape à la fois. Un programme Haskell s'apparente davantage à une fonction mathématique, dans lequel vous pouvez résoudre "l'équation" dans un certain nombre de commandes potentielles. Cela confère un certain nombre d'avantages, dont celui d'éliminer la possibilité de certains types de bogues, en particulier ceux liés à des choses comme "l'état".

cependant, il y a certains problèmes qui ne sont pas si simples à résoudre avec ce style de programmation. Certaines choses, comme la programmation de la console, et les entrées/sorties de fichiers, ont besoin que des choses se produisent dans un ordre particulier, ou besoin de maintenir l'état. Une façon de traiter ce problème est de créer un type d'objet qui représente l'état d'un calcul et d'une série de fonctions qui prennent un objet d'état en tant qu'entrée et de retour d'un nouveau modifié l'état de l'objet.

alors créons une valeur hypothétique" d'état", qui représente l'état d'un écran de console. exactement comment cette valeur est construit n'est pas important, mais disons que c'est un tableau d'octets de longueur de caractères ascii représente ce qui est actuellement visible à l'écran, et un tableau qui représente la dernière ligne entrée par l'utilisateur, en pseudo-code. Nous avons défini certaines fonctions qui prennent l'état de la console, le modifient et renvoient un nouvel état de la console.

consolestate MyConsole = new consolestate;

ainsi pour faire la programmation de la console, mais d'une manière purement fonctionnelle, vous auriez besoin de nicher beaucoup d'appels de fonction à l'intérieur eachother.

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

programmer de cette façon maintient le " pur" le style fonctionnel, tout en forçant les changements à la console à se produire dans un ordre particulier. Mais, nous allons probablement vouloir faire plus que quelques opérations à la fois comme dans l'exemple ci-dessus. Les fonctions de nidification de cette façon vont commencer à devenir impies. Ce que nous voulons, c'est un code qui fait essentiellement la même chose que ci-dessus, mais qui est écrit un peu plus comme ceci:

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

ce serait en effet une façon plus commode de l'écrire. Comment pouvons-nous le faire?

qu'est Ce qu'une monade?

une fois que vous avez un type (comme consolestate ) que vous définissez avec un tas de fonctions conçues spécifiquement pour fonctionner sur ce type, vous pouvez transformer le paquet entier de ces choses en un "monad" en définissant un opérateur comme : (bind) qui alimente automatiquement les valeurs de retour sur sa gauche, en paramètres de fonction sur sa droite, et un opérateur lift qui transforme les fonctions normales, en les fonctions qui fonctionnent avec ce type spécifique de lier l'opérateur.

comment un monad est-il mis en œuvre?

Voir d'autres réponses, qui semblent tout à fait libre de sauter dans les détails de cette.

38
répondu Breton 2017-12-12 00:43:20

(Voir aussi les réponses à qu'est Ce qu'une monade? )

une bonne motivation pour les monades est le de sigfpe (Dan Piponi) vous auriez pu inventer des monades! (Et Peut-Être Que Vous Avez Déjà) . Il y a beaucoup d'autres tutoriels monad , dont beaucoup tentent à tort d'expliquer monades en "termes simples" en utilisant diverses analogies: c'est le monad tutoriel fallacy ; les éviter.

comme dit le DR MacIver dans dites-nous Pourquoi votre langue craint :

donc, les choses que je déteste à propos de Haskell:

Commençons par l'évidence. Monade tutoriels. Non, pas des monades. Spécifiquement les tutoriels. Ils sont interminables, surannés et mon Dieu, ils sont ennuyeux. De plus, je n'ai jamais vu aucun des preuves convaincantes qu'ils aident. Lis la définition de la classe, écris un code, oublie le nom effrayant.

vous dites que vous comprenez le peut-être monad? Bon, vous êtes sur votre chemin. Il suffit de commencer à 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ématiquement, vous pourriez vouloir ignorer les douzaines de tutoriels et d'apprendre la définition, ou de suivre conférences en catégorie de la théorie :) La principale partie de la définition est qu'un Monad m implique un "constructeur de type" qui définit pour chaque type existant "T" un nouveau type "M T", et quelques façons de faire des allers-retours entre les types "réguliers" et les types "M".]

en outre, assez étonnamment, l'un des meilleurs introductions à monads est en fait l'un des premiers documents universitaires présentant monads, de Philip Wadler Monads for functional programming . Il fait A pratique, non-trivial exemples motivants, contrairement à beaucoup de tutoriels artificiels là-bas.

33
répondu ShreevatsaR 2017-05-23 11:47:32

une monade est, en fait, une forme d' "opérateur de type". Il va faire trois choses. D'abord, il sera "wrap" (ou convertir) d'une valeur d'un type en un autre type (généralement appelé un "monadique type"). En second lieu, il mettra à disposition toutes les opérations (ou fonctions) sur le type sous-jacent sur le type monadique. Enfin, il fournira un soutien pour se combiner avec une autre monade afin de produire une monade composite.

Le "peut-être monade" est essentiellement le equivalent de "nullable types" dans Visual Basic / C#. Il prend un type non nul "T" et le convertit en un "Nullable", puis définit ce que tous les opérateurs binaires signifient sur un Nullable.

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 autour des effets secondaires à mesure que les valeurs passent d'une fonction à l'autre.

on les appelle "monades "plutôt que le nom plus facile à saisir d '"opérateurs de type" pour plusieurs raisons:

  1. monades ont des restrictions sur ce qu'ils peuvent faire (voir la définition pour plus de détails).
  2. ces restrictions, ainsi que le fait qu'Il ya trois opérations impliquées, se conforme à la structure de quelque chose appelé un monad dans la théorie des catégories, qui est une branche obscure des mathématiques.
  3. Ils ont été conçus par les promoteurs de "pures" langues fonctionnelles
  4. les partisans des langages fonctionnels purs comme les branches obscures des mathématiques
  5. parce que les maths sont obscures, et les monades sont associées à des styles particuliers de programmation, les gens ont tendance à utiliser le mot monad comme une sorte de poignée de main secrète. De ce fait, personne n'a pris la peine d'investir dans un meilleur nom.
30
répondu Scott Wisniewski 2015-08-28 17:10:01

j'ai écrit ceci surtout pour moi mais j'espère que d'autres le trouveront utile:)

je crois que cette explication est plus correcte. cependant, je pense que ce traitement est encore valable et envisagera de l'incorporer à une date ultérieure. Il suffit de dire que, lorsque la composition fonctionnelle conventionnelle traite de valeurs simples de fonctions, les monades sont sur la composition de fonctions qui agissent sur les valeurs de fonction (fonctions d'ordre supérieur). Quand vous avez affaire à fonctions d'ordre supérieur (fonctions qui acceptent ou renvoient des fonctions), la composition doit être personnalisée ou taillée sur mesure afin d'évaluer les opérandes lors de l'évaluation de la composition. Ce processus d'évaluation peut être exotiques tels que la collecte des résultats de processus asynchrones. Néanmoins, cette adaptation peut être faite pour suivre un modèle. Une version de ce modèle est appelé le Monad et suit beaucoup d'addition algébrique. En particulier, en ce qui concerne le contenu, tels supérieur fonctions d'ordre serait considéré comme les opérateurs mathématiques dans l'expression accepter comme opérande d'autres partiellement appliquée opérateurs et donc les fonctions, 1+ 2*, 3/, 7+ dans 1+ ( 2* ( 3/ ( 7+ (..) ) ) ) ...

monades aborder un problème qui apparaît également en arithmétique comme division par zéro, DivByZero . Plus précisément, les calculs faisant appel à la division doivent permettre de détecter ou de tenir compte d'une exception DivByZero . Cette exigence rend le codage de telles expressions dans le cas général désordre.

la solution monadique est d'embrasser DivByZero en faisant ce qui suit

  1. étendre le type Number pour inclure DivByZero comme valeur spécifique qui n'est pas un nombre régulier: NaN , Infinity , ou Null . Appelons ce nouveau type de numéro, Nullable<Number> .
  2. fournit une fonction pour "lever" ou envelopper un Number existant" dans un Nullable<Number> (l'idée de "l'habillage" est que le contenu Number ou la valeur peut être "déballé" sans perte d'information)
  3. fournit une fonction pour" lever "ou envelopper les opérateurs existants sur Number dans une version qui fonctionne sur Nullable<Number> . Un tel opérateur "levé" pourrait simplement faire ce qui suit::
    1. déballer fournis Nullable<Number> opérandes et d'appliquer son contenu Number opérateur sur eux alors "l'ascenseur" le résultat Number résultant dans un Nullable<Number>
    2. détecter DivByZero opérande ou d'une exception lors de l'évaluation et de passer par une évaluation supplémentaire, la production d'un DivByZero valeur comme le résultat d'affirmer, 1 + Null = Null ). Toutefois, les mesures à prendre dépend du programmeur. En général, ces fonctions d'enrubannage sont l'endroit où une grande partie des fonctionnalités des monades sont écrites. L'information sur l'état monade est conservée à l'intérieur du type d'enveloppe lui-même à partir d'où les fonctions enveloppées inspectent et, selon l'approche d'immutabilité de la programmation fonctionnelle, construisent une nouvelle valeur monadique. Dans le cas de Nullable<Number> , une telle information d'état monade décrirait si DivByZero ou un Number réel existe.

ainsi, un Monad est un type élargi avec une fonction qui "enveloppe" le type original dans cette version élargie et une autre fonction qui enveloppe l'original opérateur(s) afin qu'ils puissent gérer ce nouveau type étendu. (Les monades peuvent avoir été une motivation pour les génériques ou les paramètres de type.)

il s'avère qu'au lieu de simplement lisser la manipulation de DivByZero (ou L'Infini si vous voulez), le traitement Monad est largement applicable aux situations qui peuvent bénéficier de l'expansion de type pour simplifier leur codage. En fait, cette applicabilité semble être large.

Par exemple, le IO Monad est un type qui représente l'univers, littéralement. L'intention est de reconnaître que les valeurs retournées par le programme prototypique HelloWorld ne sont pas entièrement décrites par le type de résultat de string et sa valeur " Hello World!". En fait, un tel résultat inclut également des modifications au matériel et aux États de mémoire de dispositifs tels que la console. Par exemple, après l'exécution, la console affiche maintenant du texte supplémentaire, le curseur est sur une nouvelle ligne, et donc de suite. Le IO Monad est simplement une reconnaissance explicite de tels effets externes ou secondaires, si vous voulez.

pourquoi s'embêter?

Les monades

permettent de concevoir et de documenter des algorithmes strictement apatrides. Les machines d'état sont complexes. Par exemple, une machine avec seulement 10 bits peut être dans 2^10 états possibles. Éliminer la complexité superflue est l'idéal des langages fonctionnels.

Variables d'attente. Éliminer les "variables" devrait tout simplement faire l'affaire. Les programmes purement fonctionnels ne gèrent pas les variables, mais seulement les valeurs (malgré l'utilisation du terme "variable" dans la documentation de Haskell) et utilisent plutôt des étiquettes ou des symboles ou des noms pour ces valeurs, selon les besoins. Par conséquent, la chose la plus proche d'une variable dans un langage purement fonctionnel est les paramètres reçus par une fonction car ils acceptent de nouvelles valeurs sur chaque invocation. (Une étiquette se réfère à une valeur alors que une variable se réfère à l'endroit où une valeur est tenue. Par conséquent, vous pouvez modifier le contenu d'une variable, mais une étiquette est le contenu lui-même. Ultime, il vaut mieux être donné une pomme qu'un sac avec une pomme éventuellement.)

l'absence de variables explique pourquoi les langages purement fonctionnels utilisent la récursion au lieu des boucles pour itérer. La loi de l'incrémentation d'un compteur implique l'utilisation d'une variable qui devient incrémenté et l'incertitude avec la manière dont il est mis à jour, quand il est testé, quelle valeur il devrait être et quand, et puis la complexité lorsque vous avez plusieurs threads potentiellement accéder à cette même variable.

néanmoins, Et alors?

sans la présence de l'état, une fonction doit devenir une déclaration ou une définition de ses résultats, par opposition à une inscription d'un État sous-jacent vers un résultat. Essentiellement, l'expression fonctionnelle de incFun(x) = x + 1 est plus simple que l'expression impérative de incImp(x) = x.add(1); return x; ici, incFun ne modifie pas x mais crée une nouvelle valeur. incFun peut même être remplacé par sa définition dans des expressions comme 1 + incFun(x) devenant 1 + (x + 1) . D'autre part, incImp modifie l'état de x . Quelle que soit la signification de cette modification pour x peut être imprécise et en fin de compte impossible à déterminer sans exécuter le programme en plus de toute concurrence question.

une telle complexité devient cognitivement onéreuse au fil du temps (2^n). En revanche, l'opérateur, + , ne peut pas modifier x , mais doit construire une nouvelle valeur dont le résultat est limité et entièrement déterminée par les valeurs x et 1 et la définition de + . En particulier, l'explosion de complexité de 2^n est évitée. En outre, pour souligner la simultanéité, incImp , contrairement incFun , ne peut être invoqué concurremment sans précautions autour du partage du paramètre puisqu'il est modifié par chaque invocation.

pourquoi appeler ça une monade?

une monade est caractérisée par une structure mathématique appelée un monoïde de la théorie des groupes algébriques. Cela dit, tout ce que cela signifie est qu'un monoïde a les trois propriétés suivantes:

  1. a un binaire opérateur, * , tel que x * y = z pour x, y, and z appartenant à un certain type S . Par exemple 1 ÷ 2 = 0.5 où 1, 2, et 0.5 sont tous de type Number . fermé
  2. a un élément d'identité, i , associé à l'opérateur binaire qui ne fait rien de tel que (i * x) = (x * i) = x . Par exemple l'opérateur numérique, +, et le nombre, 0, 4 + 0 = 0 + 4 = 4. identité
  3. l'ordre d'évaluation des "segments" n'est pas pertinente: (x * y) * z = x * (y * z) . Par exemple l'opérateur numérique, +, dans (3 + 4) + 12 = 3 + (4 + 12) = 19 . Notez cependant que l'ordre des termes ne doit pas changer. associativité

la propriété trois (associativité) permet d'évaluer les expressions de longueurs arbitraires en les délimitant en segments et en évaluant chaque segment indépendamment comme en parallèle. Par exemple, x1*x2*...*xN peut être segmenté en (x1..xJ) * (xJ+1...xM) * (xM+1...xN) . Le résultat séparé, x0J * xJM * xMN , peut alors être recueilli et évalué de manière similaire. La prise en charge d'une segmentation comme celle-ci est une technique clé qui garantit une concurrence et une évaluation distribuées correctes telles qu'utilisées par les algorithmes de recherche distribués de Google (a la map/reduce).

propriété deux (identité), permet une plus grande facilité dans la construction des expressions de diverses façons bien qu'il ne puisse pas être entièrement évident; cependant, de la même manière que le zéro n'était pas évidemment nécessaire aux systèmes de comptage précoces il est utile comme un concept de vide comme dans envelopper une valeur vide. Notez que dans le type, Nullable<Number> , Null n'est pas une valeur vide, mais plutôt DivByZero . Plus précisément, nn + DivByZero = DivByZero alors que nn + 0 = 0 + nn = nn , donc 0 reste l'identité sous + , où nn est tout Nullable<Number> .

enfin, il y a une raison pour laquelle nous n'utilisons plus de chiffres romains... hébergement pour zéro ou les fractions, les nombres irrationnels, les nombres négatifs, les nombres imaginaires,...ouais, il semble que notre système peut être considéré comme une monade.

29
répondu George 2018-08-28 18:34:57

monades sont de contrôler le flux de ce que les types de données abstraites sont aux 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 hachures, ou des cartes), et des arbres. Dans ces types de données, il existe de nombreux cas spéciaux (par exemple Insertionorderpreservingidentityhmap).

cependant, lorsque confrontés avec le programme "flow" beaucoup de développeurs n'ont pas été exposés à beaucoup plus de constructions que si, switch/ case, faire, alors que, goto (grr), et (peut-être), les fermetures.

ainsi, un monad est simplement un construit de flux de contrôle. Une meilleure expression pour remplacer monade serait "type de contrôle'.

en tant que tel, un monad a des slots pour la logique de contrôle, ou des déclarations, 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, le" si "monad:

if( clause ) then block

à son le plus simple a deux slots - une clause, et un bloc. Le monad if est habituellement construit pour évaluer le résultat de la clause, et si ce n'est pas faux, évaluer le bloc. Beaucoup de développeurs ne sont pas présentés à monads quand ils apprennent "si", et il n'est tout simplement pas nécessaire de comprendre monads pour écrire la 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 sémantique similaire, mais implémentations et 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.

compilateurs peuvent ou ne peuvent pas avoir le soutien pour les monades définies par l'utilisateur. Haskell est certain. Ioke possède des capacités similaires, bien que le terme monad ne soit pas utilisé dans la langue.

24
répondu 2 revs, 2 users 82%Nick Drew 2015-08-28 17:28:58

mon tutoriel préféré de Monad:

http://www.haskell.org/haskellwiki/All_About_Monads

(sur 170 000 hits sur une recherche Google pour"Monad tutorial"!)

@Stu: le but des monades est de vous permettre d'ajouter (généralement) la sémantique séquentielle au code autrement pur; vous pouvez même composer des monades (en utilisant des transformateurs Monad) et obtenir une sémantique combinée plus intéressante et compliquée, comme parsing 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), tout en fournissant une syntaxe commode pour le rendre impératif.

Haskell a déjà la surcharge de l'opérateur[1]: Il utilise des classes de type beaucoup de la façon dont on pourrait utiliser des interfaces en Java ou C# mais Haskell se trouve juste à autoriser aussi des jetons non-alphanumériques comme + & & et > as identificateurs infix. C'est seulement la surcharge d'opérateur dans votre façon de regarder si vous voulez dire "surcharger le point-virgule" [2]. Il ressemble à de la magie noire et de demander des problèmes pour "surcharger le point-virgule" (image hackers Perl entreprenant obtenir vent de cette idée), mais le point est que sans monades il n'y a pas de point-virgule, puisque le code purement fonctionnel ne nécessite pas ou ne permet pas le séquençage explicite.

tout Cela semble beaucoup plus compliqué que il en a besoin. l'article de sigfpe est assez cool mais utilise Haskell pour l'expliquer, ce qui ne réussit pas à briser le problème du poulet et de l'œuf de comprendre Haskell à grok Monads et de comprendre Monads à grok Haskell.

[1] Il s'agit d'une question distincte des monades, mais les monades utilisent la fonction de surcharge de l'opérateur de Haskell.

[2] Ceci est aussi une simplification excessive puisque l'opérateur pour enchaîner les actions monadiques est > > = (prononcé "bind") mais il y a sucre syntaxique ("do") qui vous permet d'utiliser des accolades et des points-virgule et/ou des indentations et des nouvelles lignes.

13
répondu Jared Updike 2011-05-05 21:59:50

j'ai pensé aux monades d'une manière différente, dernièrement. J'ai pensé à eux comme l'Abstraction sur ordre d'exécution d'une manière mathématique, ce qui rend de nouveaux types de polymorphisme possible.

si vous utilisez un langage impératif, et que vous écrivez quelques expressions dans l'ordre, le code s'exécute toujours exactement dans cet ordre.

et dans le cas simple, quand vous utilisez un monad, il se sent le même -- vous définissez une liste de les expressions qui se produisent dans l'ordre. Sauf que, en fonction du monad que vous utilisez, votre code peut fonctionner dans l'ordre (comme dans IO monad), en parallèle sur plusieurs éléments à la fois (comme dans la liste monad), il peut s'arrêter en partie (comme dans la Peut-être monad), il peut s'arrêter en partie pour être repris plus tard (comme dans une reprise monad), il peut revenir en arrière et recommencer depuis le début (comme dans une Transaction monad), ou il peut revenir en arrière en partie pour essayer d'autres options (comme dans une logique monad).

et parce que les monades sont polymorphiques, il est possible d'exécuter le même code dans différentes monades, en fonction de vos besoins.

Plus, dans certains cas, il est possible de combiner des monades ensemble (avec des transformateurs monad) pour obtenir plusieurs fonctionnalités en même temps.

9
répondu jes5199 2008-12-12 20:36:57

je suis encore nouveau en monade, mais j'ai pensé que je partagerais un lien que j'ai trouvé qui se sentait vraiment bien à lire (avec des photos!!): http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman / (pas d'affiliation)

fondamentalement, le concept chaud 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 travailler d'une manière composable, c.-à-d. être en mesure de ficeler plusieurs fonctions et les mélanger et les assortir sans se soucier des types de retours incohérents et autres. Donc la fonction BIND est en charge de garder des pommes avec des pommes et des oranges avec des oranges quand nous essayons de faire ces adaptateurs. Et la fonction LIFT est chargée de prendre des fonctions de "niveau inférieur" et de les "mettre à niveau" pour travailler avec des fonctions de liaison et être composable ainsi.

j'espère que j'ai bien compris, et, plus important encore, l'espoir que l'article est valide d'un point de vue sur les monades. Si rien d'autre, cet article m'a donné envie d'en savoir plus sur les monades.

9
répondu magicpanda 2015-08-29 07:38:15

en plus des excellentes réponses ci-dessus, permettez-moi de vous offrir un lien vers L'article suivant (par Patrick Thomson) qui explique monads en rapportant le concept à la bibliothèque JavaScript jQuery (et sa façon d'utiliser "méthode chaînage" pour manipuler le DOM): jQuery est un Monad

Le jQuery documentation lui-même ne fait pas référence à la notion de "monade", mais parle de "générateur de modèle" qui est probablement plus familier. Cela ne change pas le fait que vous avez une vraie monade là-bas peut-être sans même s'en rendre compte.

8
répondu siggboy 2009-01-24 14:10:13

les monades ne sont pas des métaphores , mais une abstraction pratique émergeant d'un modèle commun, comme L'explique Daniel Spiewak.

8
répondu thSoft 2010-12-27 12:48:15

une monade est une façon 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 n'y circule. Mais quand j'ai fini de assembler tous les bits avec 'bind' et 'return' alors j'invoque quelque chose comme runMyMonad monad data et les données circulent à travers les tuyaux.

6
répondu Alex 2010-06-13 12:16:56

les deux choses qui m'ont le mieux aidé quand j'ai appris qu'il y avait:

, Chapitre 8, "Fonctionnel des Analyseurs," de Graham Hutton du livre Programmation Haskell . Cela ne mentionne pas les monades du tout, en fait, mais si vous pouvez travailler à travers le chapitre et vraiment comprendre tout ce qu'il contient, en particulier comment une séquence d'opérations de bind est évaluée, vous comprendrez les internes des monades. S'attendre à prendre plusieurs essais.

le tutoriel tout sur les monades . Cela donne plusieurs bons exemples de leur utilisation, et je dois dire que l'analogie dans Appendex j'ai travaillé pour moi.

5
répondu Curt J. Sampson 2009-05-16 10:38:50

Monoid semble être quelque chose qui assure que toutes les opérations définies sur un Monoid et un type supporté retourneront toujours un type supporté à l'intérieur du Monoid. Par exemple, un nombre + nombre = nombre, pas d'erreurs.

alors que la division accepte deux fractions, et renvoie une fraction, qui définit la division par zéro comme L'infini dans Haskell somewhy (qui se trouve être une fraction somewhy)...

dans tous les cas, il semble monades sont juste un façon de s'assurer que votre chaîne d'opérations se comporte d'une manière prévisible, et une fonction qui prétend être Num -> Num, composée avec une autre fonction de Num->Num appelée avec x ne dit pas, tirer 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 aussi les missiles, parce que notre intention est claire -- nous voulons tirer les missiles -- mais elle n'essaiera pas d'imprimer "Hello World" pour quelque étrange raison.

En Haskell, le principal est de type IO (), ou IO [()], la distiction est étrange et je ne vais pas en parler mais voici ce que je pense qui se passe:

si j'ai main, 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 par IO. Ainsi, je peux enchaîner les opérations D'entrées-sorties pour -- faire des entrées-sorties, rien d'autre.

si j'essaie de faire quelque chose qui ne "retourne pas IO", le programme va se plaindre que la chaîne ne coule pas, ou essentiellement "comment est-ce lié à ce que nous essayons de faire -- une action IO", il semble forcer le programmeur à garder leur train de pensée, sans s'écarter et penser à tirer les missiles, tout en créant des algorithmes pour le tri-qui ne coule pas.

fondamentalement, les monades semblent être une astuce pour le compilateur que " hey, vous savez cette fonction qui renvoie un nombre ici, il ne fonctionne pas réellement toujours, il peut parfois produire un nombre, et parfois rien du tout, il suffit de garder cela à l'esprit". Sachant cela, si vous essayez d'affirmer une action monadique, l'action monadique peut agir comme une exception de temps de compilation disant "Hé, ce n'est pas vraiment un nombre, ça peut être un nombre, mais vous ne pouvez pas supposer cela, faire quelque chose pour s'assurer que le flux est acceptable."qui empêche le comportement imprévisible du programme -- dans une certaine mesure.

il semble que les monades ne sont pas sur la pureté, ni le contrôle, mais sur maintenir une identité d'une catégorie sur laquelle tout comportement est prévisible et défini, ou ne compile pas. Vous ne pouvez rien faire quand on s'attend à ce que vous fassiez quelque chose, et vous ne pouvez rien faire si on s'attend à ce que vous ne fassiez rien (visible).

la plus grande raison à laquelle je pourrais penser pour les monades est -- allez voir le code de procédure / OOP, et vous remarquerez que vous ne savez pas où le programme commence, ni se termine, tout ce que vous voyez est beaucoup de saut et beaucoup de mathématiques, magie,et missile. Vous ne serez pas en mesure de le maintenir, et si vous le pouvez, vous passerez beaucoup de temps à envelopper votre esprit autour du programme entier avant que vous ne puissiez comprendre n'importe quelle partie de celui-ci, parce que la modularité dans ce contexte est basée sur des "sections" de code interdépendantes, où le code est optimisé pour être aussi lié que possible pour promesse d'efficacité/inter-relation. Les monades sont très concrètes, et bien définies par définition, et assurent que le flux du programme est possible à analyser, et isoler les pièces qui sont difficiles à analyser -- comme ils sont eux-mêmes des monades. Une monade semble être une "unité compréhensible qui est prévisible sur sa pleine compréhension" -- si vous comprenez" peut-être "la monade, il n'y a aucune façon possible qu'elle fasse quoi que ce soit sauf être" peut-être", ce qui semble trivial, mais dans la plupart des codes non monadiques, une simple fonction" helloworld " peut tirer les missiles, ne rien faire, ou détruire l'univers ou même fausser le temps -- nous n'avons aucune idée ni aucune garantie que c'est ce que c'est. Une monade GARANTIT qu'IL EST CE qu'IL EST. ce qui est très puissant.

toutes les choses du" monde réel " semblent être des monades, en ce sens qu'elles sont liées par des lois précises et observables empêchant la confusion. Cela ne signifie pas que nous devons imiter toutes les opérations de cet objet pour créer des classes, au lieu de cela nous pouvons simplement dire "un carré est un carré", rien mais un carré, pas même un rectangle ou un cercle, et "un carré a la superficie de la longueur de l'une de ses dimensions existantes multiplié par m'. Peu importe le carré que vous avez, si c'est un carré dans l'espace 2D, sa superficie ne peut absolument pas être autre chose que sa longueur au carré, il est presque trivial de prouver. C'est très puissant parce que nous n'avons pas besoin de faire des assertions pour nous assurer que notre monde est comme il est, nous utilisons juste les implications de la réalité pour empêcher nos programmes de tomber sur la bonne voie.

Je suis à peu près sûr d'avoir tort, mais je pense que cela pourrait aider quelqu'un là-bas, donc j'espère que ça aide quelqu'un.

5
répondu Dmitry 2013-03-16 22:01:43

dans le contexte de Scala vous trouverez la définition suivante pour être 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
  }
}

E. G.

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

NOTE Strictement parlant, la définition d'un Monade dans la programmation fonctionnelle n'est pas la même que la définition d'un Monade dans la Catégorie "Théorie 1519120920" , qui est défini dans les virages de map et flatten . Bien qu'ils soient en quelque sorte équivalents sous certaines mappings. Ces présentations sont très bonnes: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

5
répondu samthebest 2014-04-11 13:01:35

en pratique, monad est une implémentation personnalisée de l'opérateur de composition de fonction qui prend en charge les effets secondaires et les valeurs d'entrée et de retour incompatibles (pour chaînage).

5
répondu Mateusz Charytoniuk 2015-11-09 13:11:00

cette réponse commence par un exemple motivant, travaille à travers l'exemple, dérive un exemple d'une monade, et définit formellement"monad".

tenir compte de ces trois fonctions dans le pseudocode:

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

f prend une paire commandée du formulaire <x, messages> et retourne une paire commandée. Il laisse le premier article intact et ajoute "called f. " au deuxième article. Idem pour g .

Vous pouvez composer ces fonctions et obtenir votre valeur originale, ainsi qu'une chaîne qui montre 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 que f et g sont responsables d'ajouter leurs propres messages de log aux informations de logging précédentes. (Imaginez juste pour le bien de l'argument qu'au lieu d'ajouter des cordes, f et g doit effectuer la 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 quand 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. Mais si vous pouviez nourrir 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) que "nourrir m en f ". À alimenter une paire <x, messages> dans une fonction f est de passer x dans f , obtenir <y, message> de f , et retourner <y, messages message> .

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

notez ce qui se passe quand vous faites trois choses avec vos fonctions:

Premier: si vous enveloppez une valeur et puis nourrir la paire résultante dans 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)

qui est le même que passant la valeur dans la fonction.

Deuxième: si vous nourrissez une paire en 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>

Qui ne change pas la paire.

troisième: si vous définissez une fonction qui prend x et alimente g(x) en f :

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

et d'y introduire 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>))

qui est le même Que l'alimentation de la paire dans g et l'alimentation de la paire dans f .

vous avez la plupart d'une monade. Maintenant, vous avez juste besoin de savoir sur les types de données dans votre programme.

quel type de valeur est <x, "called f. "> ? Ça dépend du type de valeur x . Si x est de type t , puis votre paire est une valeur de type "paire de t et la chaîne". Appelez ce type M t .

M est un constructeur de type: M seul ne se réfère pas à un type, mais M _ se réfère à un type une fois que vous remplissez le blanc avec un type. Un M int est une paire d'int et une corde. Un M string est une paire de cordes. Etc.

Félicitations, vous avez créé une monade!

officiellement, votre monade est le tuple <M, feed, wrap> .

un monad est un tuple <M, feed, wrap> où:

  • M est un constructeur de type.
  • feed prend une (fonction qui prend un t et renvoie un M u ) et un M t et renvoie un M u .
  • wrap prend un v et retourne un M v .

t , u , et v sont trois types différents. Un monad satisfait aux trois propriétés que vous avez prouvées pour votre monad spécifique:

  • L'alimentation a enveloppé t dans une fonction est la même que passant le t dans la fonction.

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

  • alimenter un M t en wrap ne fait rien au M t .

    formellement: feed(wrap, m) = m

  • alimenter un M t (l'appeler m ) dans une fonction qui

    • passe le t en g
    • obtient un M u (l'appeler n ) de g
    • alimente n en f

    est le même que

      "15191490920 de" nourrir m en g
    • arriver n à partir de g
    • "15191490920 de" nourrir n en f

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

typiquement, feed est appelé bind (alias >>= in Haskell) et wrap est appelé return .

5
répondu Jordan 2016-05-26 21:13:59

si j'ai bien compris, IEnumerable est dérivé de monades. Je me demande si cela pourrait être un angle d'approche intéressant pour ceux d'entre nous du C# world?

pour ce que ça vaut, voici quelques liens vers des tutoriels qui m'ont aidé (et Non, Je n'ai toujours pas compris ce que sont les monades).

4
répondu Benjol 2008-09-16 12:06:31

le monde a besoin d'une autre monade blog, mais je pense que c'est utile pour identifier les monades dans la nature.

Sierpinski triangle

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

monades 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 à la programmation, et c'est pourquoi il atteint son apogée dans de nombreuses situations.

3
répondu Eugene Yokota 2014-10-23 14:40:26

http://code.google.com/p/monad-tutorial/ est un travail en cours, pour répondre exactement à cette question.

3
répondu Tony Morris 2015-08-28 17:30:03

je vais essayer d'expliquer Monad dans le contexte de Haskell.

dans la programmation fonctionnelle, la composition fonctionnelle est importante. Il permet à notre programme de se composer de petites fonctions faciles à lire.

disons que nous avons deux fonctions: g :: Int -> String et f :: String -> Bool .

nous pouvons faire (f . g) x , qui est juste la même que f (g x) , où x est une valeur Int .

Lorsqu'on compose/applique le résultat d'une fonction à une autre, il est important que les types concordent. Dans le cas ci-dessus, le type du résultat retourné par g doit être le même que le type de acceptés par f .

Mais parfois, les valeurs sont dans des contextes, et cela rend un peu moins facile d'aligner les types. (Avoir des valeurs dans les contextes est très utile. Par exemple, le type Maybe Int représente une valeur Int qui peut ne pas être là, le type IO String représente une valeur String qui est là à la suite de l'exécution de certains effets secondaires.)

disons que nous avons maintenant g1 :: Int -> Maybe String et f1 :: String -> Maybe Bool . g1 et f1 sont très similaires à g et f , respectivement.

nous ne pouvons pas faire (f1 . g1) x ou f1 (g1 x) , où x est une valeur Int . Le type du résultat retourné par g1 n'est pas ce f1 attend.

nous pourrions composer f et g avec l'opérateur . , mais maintenant nous ne pouvons pas composer f1 et g1 avec . . Le problème est que nous ne pouvons pas carrément passer une valeur dans un contexte à une fonction qui attend une valeur qui n'est pas dans un contexte.

ne serait-il pas agréable si nous introduisons un opérateur pour composer g1 et f1 , de sorte que nous pouvons écrire (f1 OPERATOR g1) x ? g1 renvoie une valeur dans un contexte. La valeur sera prise hors contexte et appliquée à f1 . Et oui, nous avons un tel opérateur. C'est <=< .

nous avons aussi l'opérateur >>= qui fait exactement la même chose pour nous, bien que dans une syntaxe légèrement différente.

nous écrivons: g1 x >>= f1 . g1 x est une valeur de Maybe Int . L'opérateur >>= aide à retirer la valeur Int 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 l'ensemble de la "1519370920 de l'opération".

et enfin, pourquoi Monad est-il utile? Parce que Monad est la classe de type qui définit l'opérateur >>= , très semblable à la classe de type Eq qui définit les opérateurs == et /= .

pour conclure, le Monad classe type définit l'opérateur >>= qui nous permet de passer des valeurs dans un contexte (nous appelons ces valeurs monadiques) à des fonctions qui n'attendent pas de valeurs dans un contexte. Le contexte est pris en charge.

S'il y a une chose à retenir ici, c'est que Monad s permettent la composition de fonction qui implique des valeurs dans les contextes .

3
répondu Jonas 2017-08-04 17:53:56

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'application de l'opérateur $ de fonctions

forall a b. a -> b

est défini canoniquement

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

infixr 0 $

en termes de Haskell-primitive de la fonction de demande f x ( infixl 10 ). La Composition . est définie en termes de $ comme

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

infixr 9 .

et satisfait à la equivalences 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 associatif, et id est son identité droite et gauche.

La Kleisli triple

dans la programmation, un monad est un constructeur de type functor avec une instance de la classe de type monad. 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 monade.

un functor est un constructeur de type f 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 statically enforced, les instances de la classe de type functor doivent obéir à la loi algébrique functor laws "15192190920 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

Functor calculs ont le type

forall f t. Functor f => f t

Un calcul c r consiste à résultats r à l'intérieur contexte c .

Unaire monadique de fonctions ou de Kleisli flèches ont le type

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

les flèches Kleisi sont des fonctions qui prennent un argument a et renvoient un calcul monadique m b .

monades sont canoniquement défini en termes de la Kleisli triple forall m. Functor m =>

(m, return, (=<<))

mis en œuvre, comme le type de la classe

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

infixr 1 =<<

la identité Kleisli" 15192190920 return est une flèche Kleisli qui promeut une valeur t dans un contexte monadique m . Extension ou Kleisli application =<< s'applique une flèche Kleisli a -> m b vers les résultats d'un calcul m a .

composition Kleisli <=< est défini 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 de gauche aux résultats de l'application de la flèche de droite.

les Instances de la classe de type monad doivent obéir aux lois monad , la plupart élégamment exprimé en termes de Kleisli composition: forall f g h.

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

<=< est associatif, et return est 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 un functor,

   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)

en canonique Haskell, la monade d'identité est défini

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 le calcul Maybe t qui ne peut pas donner un résultat t , le calcul qui peut"échouer". L'option monad 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 b n'est appliqué que si Maybe a donne un résultat.

newtype Nat = Nat Int

le naturel les nombres peuvent être codées comme les 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 sous soustraction.

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

infixl 6 -?

l'option monad couvre une forme basique de traitement d'exception.

(-? 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" annexe"

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

infixr 5 ++

encode non-linéaire calcul [t] en 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
   _ =<< []       = []

Extension concatène ++ toutes les listes de résultats [b] à partir d'applications f x de Kleisli flèche a -> [b] à des éléments de [a] en une seule liste de résultats [b] .

que le diviseurs appropriés d'un entier positif n be

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  =  []

en définissant la classe de type monad, au lieu de l'extension =<< , la norme Haskell utilise son flip, le bind operator >>= .

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

   fail :: String -> m a
   fail s = errorWithoutStackTrace s

Pour simplicitys souci, cette explication utilise le type de hiérarchie de classe

class              Functor f
class Functor m => Monad m

en Haskell, la hiérarchie standard actuelle est

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

parce que non seulement chaque monade un foncteur, mais chaque applicative est un foncteur et chaque monade un applicative, trop.

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 faire bloc

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

l'équivalent compréhension 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) >>
            return p
   )
)

Faire de la notation et de l'errance de compréhensions sont sucre syntaxique pour imbriquée lier les expressions. L'opérateur bind est utilisé pour la liaison locale 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 "empty tuple"

data () = ()

monades additives supportant choice et failure peut être résumé en utilisant 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 = []
   (<|>) = (++)

fail et <|> forment un monoïde forall k l m.

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

et fail est l'élément zéro absorbant/annihilant monades additives

_ =<< fail  =  fail

si dans

guard (even p) >> return p

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

\ _ -> return p

est appliqué au résultat () . Si false, alors la garde produit la liste monad's fail [] , qui ne donne aucun résultat pour une flèche Kleisli à appliquer >> de.

État

Infâme, les monades sont utilisés pour coder stateful de calcul.

Un état du processeur est une fonction

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

qui fait la transition d'un État st et donne un résultat t . Le état st peut être n'importe quoi. Rien, drapeau, compte, tableau, poignée, machine, monde.

le type de processeurs d'état est généralement appelé

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

le processeur d'état monad est le kinded * -> * functor State st . Les flèches Kleisli du processeur d'état monad sont des fonctions

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

dans Haskell canonique, la version paresseuse du processeur d'état monad est défini

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'État de l'accès est fourni par primitives get et put , les méthodes d'abstraction au-dessus stateful monades:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

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

m -> st , déclare un dépendance fonctionnelle de l'état de type st sur la monade m ; qu'un State t , par exemple, permettra de déterminer l'état de type t univoque.

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 à void en 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 des accesseurs de champ d'enregistrement.

l'équivalent monade de l'état de la variable filetage

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

s0 :: Int , est également transparent référentiellement, 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 () , à l'exception de 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 axée sur l'expression (par exemple Rust), le dernier énoncé d'un bloc représente son rendement. L'opérateur bind est parfois appelé "point-virgule programmable".

les primitives de la structure de contrôle par itération de la programmation structurée impérative sont émulées monadiquement

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

Le I/O de l'état du processeur monade est une réconciliation de pure Haskell et le monde réel, de la fonctionnelle de denotative et impératif d'une sémantique opérationnelle. Un proche analogue de la mise en œuvre stricte effective:

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

Interaction est facilitée par impure primitives

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 IO primitives est protocolisée en permanence par le système de type. Parce que la pureté est impressionnante , ce qui se passe dans IO , reste dans IO .

unsafePerformIO :: IO t -> t

ou, du moins, devrait.

la signature type d'un programme Haskell

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

devient

World -> ((), World)

Une fonction qui transforme un monde.

Épilogue

la catégorie dont les objets sont des types Haskell et dont les morphismes sont des fonctions entre les types Haskell est," rapide et lâche", la catégorie Hask .

un "functor T est une correspondance d'une catégorie C à une catégorie D ; pour chaque objet en C un objet dans le D

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

et pour chaque morphisme dans C un morphisme dans D

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

X , Y sont des objets dans C . HomC(X, Y) est la classe d'homomorphisme de tous les morphismes X -> Y dans C . Le foncteur doit préserver le morphisme, l'identité et la composition, la "structure" C , dans 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 C est 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 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)

est donné un morphisme dans Hask 's Kleisli catégorie

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

Composition dans la catégorie Kleisli .T est donné en termes d'extension

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

et répond à la catégorie axiomes

       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, s'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ées 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

monades peuvent également être définis en termes non D'extension Kleislian, mais une transformation naturelle mu , dans la programmation appelée join . Un monad est défini en termes de mu comme un triple sur une catégorie C , d'un endofuncteur

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

et deux naturelles tranformations15192020920"

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

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

satisfaisant aux é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 monad est alors définie

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

canoniques mu la mise en œuvre de l'option de la monade:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

la concat fonction

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

est le join de la liste monade.

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

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

Les implémentations de join peuvent ê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 mu au formulaire d'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 abstraite être d'aucun usage pour la programmation?

La réponse est simple: comme ordinateur scientifiques, nous abstraction valeur ! Lorsque nous concevons l'interface d'un composant logiciel, nous want il pour révéler le moins possible sur la mise en œuvre. Nous voulons être en mesure de remplacer la mise en œuvre 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 le la généralité du concept monad que nous apprécions tellement, c'est parce que la théorie des catégories est tellement 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 ait aussi un lien étroit avec la théorie des catégories. Mais nous soulignons que notre but est très pratique: il ne s'agit pas de "mettre en œuvre la théorie des catégories", il s'agit de trouver une manière plus générale de structurer bibliothèques combinatrices. C'est tout simplement notre chance que les mathématiciens ont déjà fait beaucoup de travail pour nous!

à partir de la Généralisation des Monades de Flèches par John Hughes

3
répondu 6 revs, 2 users 96%user6428287 2018-05-10 15:30:11

expliquer les monades semble être comme expliquer les énoncés de flux de contrôle. Imaginez qu'un non-programmeur vous demande de les expliquer?

vous pouvez leur donner une explication impliquant la théorie - logique booléenne, valeurs de registre, pointeurs, piles, et les cadres. Mais que ce serait de la folie.

vous pourriez les expliquer en termes de syntaxe. Fondamentalement, tous les énoncés de contrôle-flux en C ont des crochets bouclés, et vous pouvez distinguer la condition et le code conditionnel par où ils sont relatifs aux parenthèses. Qui peut être encore plus fou.

ou vous pourriez également expliquer les boucles, si les déclarations, routines, sous-Programmes, et éventuellement Co-routines.

monades peuvent remplacer un assez grand nombre de techniques de programmation. Il y a une syntaxe spécifique dans les langues qui les supportent, et certaines théories à leur sujet.

ils sont également un moyen pour les programmeurs fonctionnels d'utiliser le code impératif sans vraiment l'admettre, mais ce n'est pas leur seul usage.

2
répondu wisty 2011-01-14 07:08:23

Princesse 'explication de F# Calcul des Expressions m'a aidé, même si je ne peux pas encore dire que j'ai vraiment compris.

EDIT : cette série - expliquant les monades avec javascript - est celle qui "a fait pencher la balance" pour moi.

je pense que comprendre les monades est quelque chose qui vous effraie. En ce sens, lire autant de "tutoriels" que vous pouvez est une bonne idée, mais souvent des choses étranges (langage ou syntaxe peu familière) empêche votre cerveau de se concentrer sur l'essentiel.

certaines choses que j'ai eu de la difficulté à comprendre:

  • les explications basées sur des règles n'ont jamais fonctionné pour moi, parce que la plupart des exemples pratiques en fait, exiger plus que simplement retourner/lier.
  • aussi, les appelant règles n'ont pas aidé. C'est plus une affaire de "il y a ces choses qui ont quelque chose en commun, appelons les choses "monades", et les bits en commun "règles".
  • Return ( a -> M<a> ) et Bind ( M<a> -> (a -> M<b>) -> M<b> ) sont grands, mais ce que je ne pourrais jamais comprendre est comment Bind pourrait extraire le a à partir de M<a> afin de passer en a -> M<b> . Je ne pense pas que j'ai jamais lu n'importe où (peut-être il est évident pour tout le monde), que le revers du retour ( M<a> -> a ) a pour exister à l'intérieur le monad, il n'a tout simplement pas besoin d'être exposé.
2
répondu Benjol 2017-05-23 10:31:37