Question Monade en anglais ordinaire? (Pour le programmeur POO sans arrière-plan FP)


En termes qu'un programmeur POO comprendrait (sans arrière-plan de programmation fonctionnelle), qu'est-ce qu'une monade?

Quel problème résout-il et quels sont les endroits les plus communs où il est utilisé?

MODIFIER:

Pour clarifier le type de compréhension que je cherchais, disons que vous étiez en train de convertir une application de PF qui avait des monades en une application POO. Que feriez-vous pour transférer les responsabilités des monades à l'application POO?


563
2018-04-24 13:42


origine


Réponses:


MISE À JOUR: Cette question a fait l'objet d'une série de blog immensément longue, que vous pouvez lire à Monades - Merci pour la bonne question!

En termes qu'un programmeur POO comprendrait (sans arrière-plan de programmation fonctionnelle), qu'est-ce qu'une monade?

Une monade est un "amplificateur" de types cette obéit à certaines règles et qui a certaines opérations fournies.

D'abord, qu'est-ce qu'un "amplificateur de types"? J'entends par là un système qui vous permet de prendre un type et de le transformer en un type plus spécial. Par exemple, en C # considèrent Nullable<T>. C'est un amplificateur de types. Il vous permet de prendre un type, disons int, et ajouter une nouvelle capacité à ce type, à savoir, que maintenant il peut être nul quand il ne pouvait pas auparavant.

Comme deuxième exemple, considérez IEnumerable<T>. C'est un amplificateur de types. Il vous permet de prendre un type, disons, string, et ajoutez une nouvelle capacité à ce type, à savoir, que vous pouvez maintenant faire une séquence de chaînes de n'importe quel nombre de chaînes simples.

Quelles sont les "certaines règles"? Brièvement, il existe un moyen sensé pour les fonctions sur le type sous-jacent de travailler sur le type amplifié de sorte qu'elles suivent les règles normales de la composition fonctionnelle. Par exemple, si vous avez une fonction sur les entiers, dites

int M(int x) { return x + N(x * 2); }

puis la fonction correspondante sur Nullable<int> peut faire en sorte que tous les opérateurs et les appels y travaillent ensemble "de la même manière" qu'avant.

(C'est incroyablement vague et imprécis, vous avez demandé une explication qui ne supposait rien sur la connaissance de la composition fonctionnelle.)

Quelles sont les "opérations"?

  1. Il y a une opération "unité" (parfois appelée parfois "l'opération de retour") qui prend une valeur d'un type simple et crée la valeur monadique équivalente. Ceci, en substance, fournit un moyen de prendre une valeur d'un type non amplifié et de le transformer en une valeur du type amplifié. Il pourrait être implémenté en tant que constructeur dans une langue OO.

  2. Il existe une opération "bind" qui prend une valeur monadique et une fonction qui peut transformer la valeur et renvoie une nouvelle valeur monadique. La liaison est l'opération clé qui définit la sémantique de la monade. Il nous permet de transformer des opérations sur le type non amplifié en opérations sur le type amplifié, qui obéit aux règles de composition fonctionnelle mentionnées précédemment.

  3. Il existe souvent un moyen de sortir le type non amplifié du type amplifié. Strictement parlant, cette opération n'est pas obligatoire pour avoir une monade. (Bien que cela soit nécessaire si vous voulez avoir comonad. Nous ne les examinerons pas plus en détail dans cet article.)

Encore une fois, prenez Nullable<T> par exemple. Vous pouvez transformer un int dans une Nullable<int> avec le constructeur. Le compilateur C # prend soin de la plupart des "levages" pour vous, mais si ce n'est pas le cas, la transformation de levage est simple: une opération, disons,

int M(int x) { whatever }

est transformé en

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

Et en tournant un Nullable<int> de retour dans un int est fait avec le Value propriété.

C'est la transformation de la fonction qui est le bit clé. Notez comment la sémantique réelle de l'opération nullable - qu'une opération sur un null propage le null - est capturé dans la transformation. Nous pouvons généraliser cela.

Supposons que vous ayez une fonction de int à int, comme notre original M. Vous pouvez facilement faire cela dans une fonction qui prend un int et renvoie un Nullable<int> car vous pouvez simplement exécuter le résultat via le constructeur Nullable. Supposons maintenant que vous avez cette méthode d'ordre supérieur:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

Voyez ce que vous pouvez faire avec ça? Toute méthode qui prend un int et renvoie un int, ou prend un int et renvoie un Nullable<int> peut maintenant avoir la sémantique nullable qui lui est appliquée.

En outre: supposons que vous avez deux méthodes

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

et vous voulez les composer:

Nullable<int> Z(int s) { return X(Y(s)); }

C'est, Z est la composition de X et Y. Mais tu ne peux pas faire ça parce que X prend un int, et Y retourne un Nullable<int>. Mais puisque vous avez l'opération "bind", vous pouvez faire ce travail:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

L'opération de liaison sur une monade est ce qui fait fonctionner la composition de fonctions sur les types amplifiés. Les "règles" que j'ai manipulées ci-dessus sont que la monade préserve les règles de la composition normale des fonctions; la composition avec des fonctions d'identité aboutit à la fonction originale, cette composition est associative, etc.

En C #, "Bind" s'appelle "SelectMany". Jetez un oeil à la façon dont cela fonctionne sur la séquence monad. Nous devons avoir deux choses: transformer une valeur en une séquence et lier des opérations sur des séquences. En prime, nous avons également "retourner une séquence dans une valeur". Ces opérations sont:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

La règle de la monade Nullable était de "combiner deux fonctions qui produisent ensemble des valeurs nulles, vérifier si la valeur intérieure donne null, sinon, produire une valeur nulle, si ce n'est pas le cas, alors appeler la fonction externe avec le résultat". C'est la sémantique désirée de nullable.

La règle de la monade de séquence est de "combiner deux fonctions qui produisent des séquences ensemble, d'appliquer la fonction externe à chaque élément produit par la fonction interne, puis de concaténer toutes les séquences résultantes ensemble". La sémantique fondamentale des monades est capturée dans le Bind/SelectMany méthodes c'est la méthode qui vous dit ce que la monade vraiment veux dire.

Nous pouvons faire encore mieux. Supposons que vous ayez une séquence d'ints, et une méthode qui prend en entrée et qui donne des suites de chaînes. Nous pourrions généraliser l'opération de liaison pour permettre la composition de fonctions qui prennent et retournent différents types amplifiés, tant que les entrées de l'une correspondent aux sorties de l'autre:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

Alors maintenant nous pouvons dire "amplifier ce groupe d'entiers individuels dans une séquence d'entiers." Transformer cet entier particulier en un groupe de cordes, amplifié à une séquence de chaînes.Maintenant ensemble les deux opérations: amplifier ce groupe d'entiers dans la concaténation de toutes les séquences de cordes. " Les monades vous permettent de composer vos amplifications.

Quel problème résout-il et quels sont les endroits les plus communs où il est utilisé?

C'est un peu comme demander "quels problèmes le modèle singleton résout-il?", Mais je vais essayer.

Les monades sont généralement utilisées pour résoudre des problèmes tels que:

  • J'ai besoin de faire de nouvelles fonctionnalités pour ce type tout en combinant les anciennes fonctions de ce type pour utiliser les nouvelles fonctionnalités.
  • J'ai besoin de capturer un tas d'opérations sur les types et de représenter ces opérations comme des objets composables, construisant des compositions de plus en plus grandes jusqu'à ce que j'ai juste la bonne série d'opérations représentées, et puis je dois commencer à obtenir des résultats
  • Je dois représenter proprement les opérations secondaires dans un langage qui déteste les effets secondaires

C # utilise des monades dans sa conception. Comme déjà mentionné, le modèle nullable est très proche de la "peut-être monade". LINQ est entièrement construit à partir de monades; la SelectMany méthode est ce que fait le travail sémantique de la composition des opérations. (Erik Meijer aime souligner que chaque fonction LINQ pourrait être implémentée par SelectMany; tout le reste est juste une commodité.)

Pour clarifier le type de compréhension que je cherchais, disons que vous étiez en train de convertir une application de PF qui avait des monades en une application POO. Que feriez-vous pour transférer les responsabilités des monades dans l'application POO?

La plupart des langages OOP ne disposent pas d'un système de type assez riche pour représenter directement le modèle de monad; vous avez besoin d'un système de type qui supporte les types qui sont des types plus élevés que les types génériques. Donc je n'essaierais pas de faire ça. J'implémenterais plutôt des types génériques qui représentent chaque monade, et implémenterais des méthodes qui représentent les trois opérations dont vous avez besoin: transformer une valeur en une valeur amplifiée, transformer (peut-être) une valeur amplifiée en valeur et transformer une fonction sur des valeurs non amplifiées en une fonction sur les valeurs amplifiées.

Un bon point de départ est de savoir comment nous avons implémenté LINQ en C #. Étudier le SelectMany méthode; C'est la clé pour comprendre comment la monade de séquence fonctionne en C #. C'est une méthode très simple, mais très puissante!


Suggéré, lecture supplémentaire:

  1. Pour une explication plus approfondie et théorique des monades en C #, je recommande fortement mon (Eric Lippert's) l'article du collègue Wes Dyer sur le sujet. Cet article est ce qui m'a expliqué les monades quand elles ont finalement "cliqué" sur moi.
  2. Une bonne illustration de pourquoi vous pourriez vouloir une monade autour (utilise Haskell dans ses exemples).
  3. Sort of, "traduction" de l'article précédent en JavaScript.


608
2018-01-25 10:42



Pourquoi avons-nous besoin de monades?

  1. Nous voulons programmer seulement en utilisant des fonctions. ("programmation fonctionnelle" après tout -FP).
  2. Ensuite, nous avons un premier gros problème. C'est un programme:

    f(x) = 2 * x

    g(x,y) = x / y

    Comment pouvons-nous dire ce qui doit être exécuté en premier? Comment pouvons-nous former une séquence ordonnée de fonctions (c.-à-d. un programme) en utilisant pas plus de fonctions?

    Solution: composer des fonctions. Si vous voulez d'abord g et alors f, Ecrivez f(g(x,y)). OK mais ...

  3. Plus de problèmes: certaines fonctions pourrait échouer (c'est à dire. g(2,0), diviser par 0). Nous avons aucune "exception" dans la PF. Comment pouvons-nous le résoudre?

    Solution: Nous allons permettre aux fonctions de retourner deux types de choses: au lieu d'avoir g : Real,Real -> Real (Fonction de deux réels en un réel), permettons g : Real,Real -> Real | Nothing (Fonction de deux réels en (réel ou rien)).

  4. Mais les fonctions devraient (pour être plus simples) retourner seulement une chose.

    Solution: créons un nouveau type de données à renvoyer, un "type de boxe"qui enferme peut-être un réel ou ne soit simplement rien. g : Real,Real -> Maybe Real. OK mais ...

  5. Que se passe-t-il maintenant f(g(x,y))? f n'est pas prêt à consommer Maybe Real. Et, nous ne voulons pas changer toutes les fonctions avec lesquelles nous pourrions nous connecter g consommer un Maybe Real.

    Solution: nous allons avoir une fonction spéciale pour "connecter" / "composer" / "lien" fonctions. De cette façon, nous pouvons, dans les coulisses, adapter la sortie d'une fonction pour alimenter la suivante.

    Dans notre cas: g >>= f (connecter / composer g à f). Nous voulons >>= obtenir gsortie, l'inspecter et, au cas où il est Nothing juste n'appelle pas f et retour Nothing; ou au contraire, extraire la boîte Real et nourrir f avec ça. (Cet algorithme est juste la mise en œuvre de >>= pour le Maybe type).

  6. Beaucoup d'autres problèmes surgissent qui peuvent être résolus en utilisant ce même modèle: 1. Utilisez une "boîte" pour codifier / stocker différentes significations / valeurs, et avoir des fonctions comme g qui renvoient ces "valeurs encadrées". 2. Avoir des compositeurs / lieurs g >>= f pour aider à se connecter gLa sortie de fde l'entrée, donc nous ne devons pas changer f du tout.

  7. Les problèmes remarquables qui peuvent être résolus en utilisant cette technique sont:

    • avoir un état global que toutes les fonctions de la séquence de fonctions ("le programme") peuvent partager: solution StateMonad.

    • Nous n'aimons pas les "fonctions impures": des fonctions qui donnent différent sortie pour même contribution. Par conséquent, marquons ces fonctions, en leur faisant retourner une valeur étiquetée / encadrée: IO monade.

Total bonheur


213
2017-07-17 00:14



En termes qu'un programmeur POO serait   comprendre (sans aucune fonctionnalité   arrière-plan de programmation), qu'est-ce qu'un   monade?

Quel problème résout-il et quel   sont les endroits les plus communs où il est utilisé? sont les endroits les plus communs, il est utilisé?

En termes de programmation OO, une monade est une interface (ou plus probablement un mixin), paramétrée par un type, avec deux méthodes, return et bind qui décrivent:

  • Comment injecter une valeur pour obtenir un valeur monadique de cette valeur injectée type;
  • Comment utiliser une fonction qui fait une valeur monadique d'un non monadique, sur une valeur monadique.

Le problème qu'il résout est le même type de problème que vous attendez d'une interface, à savoir, "J'ai un tas de classes différentes qui font des choses différentes, mais semblent faire ces différentes choses d'une manière qui a une similitude sous-jacente.Comment puis-je décrire cette similitude entre eux, même si les classes elles-mêmes ne sont pas vraiment des sous-types de plus proche que la classe 'l'objet' elle-même? "

Plus précisément, le Monad "interface" est similaire à IEnumerator ou IIterator en ce qu'il prend un type qui lui-même prend un type. Le principal "point" de Monad Cependant, il est possible de relier des opérations basées sur le type d'intérieur, même au point d'avoir un nouveau «type interne», tout en conservant - ou même en améliorant - la structure d'information de la classe principale.


58
2018-05-19 17:58



Je dirais que l'analogie OO la plus proche des monades est la "modèle de commande".

Dans le modèle de commande, vous enroulez une instruction ou une expression ordinaire dans un commander objet. L'objet de commande expose un exécuter méthode qui exécute l'instruction enveloppée. Ainsi, les déclarations sont transformées en objets de première classe qui peuvent être transmis et exécutés à volonté. Les commandes peuvent être composé Vous pouvez donc créer un objet-programme en enchaînant et en imbriquant des objets de commande.

Les commandes sont exécutées par un objet séparé, le invocateur. L'avantage de l'utilisation du modèle de commande (plutôt que d'exécuter simplement une série d'instructions ordinaires) est que différents invokers peuvent appliquer une logique différente à la façon dont les commandes doivent être exécutées.

Le modèle de commande peut être utilisé pour ajouter (ou supprimer) des fonctionnalités de langue qui ne sont pas prises en charge par le langage hôte. Par exemple, dans un langage OO hypothétique sans exceptions, vous pouvez ajouter une sémantique d'exception en exposant les méthodes "try" et "throw" aux commandes. Lorsqu'une commande appelle throw, l'invocateur effectue un retour arrière dans la liste (ou l'arborescence) des commandes jusqu'au dernier appel "try". Inversement, vous pouvez supprimer la sémantique d'exception d'une langue (si vous pensez les exceptions sont mauvaises) en interceptant toutes les exceptions lancées par chaque commande et en les transformant en codes d'erreur qui sont ensuite passés à la commande suivante.

Des sémantiques d'exécution encore plus fantaisistes comme des transactions, des exécutions non déterministes ou des continuations peuvent être implémentées de la sorte dans un langage qui ne le supporte pas nativement. C'est un modèle assez puissant si vous y réfléchissez.

Maintenant, en réalité, les modèles de commande ne sont pas utilisés comme une caractéristique générale du langage comme celle-ci. L'overhead de transformer chaque déclaration en classe distincte conduirait à une quantité insupportable de code standard. Mais en principe, il peut être utilisé pour résoudre les mêmes problèmes que les monades sont utilisés pour résoudre dans fp.


48
2017-07-17 22:39



Vous avez une présentation récente "Monadologie - aide professionnelle sur l'anxiété de type" par Christopher League (12 juillet 2010), ce qui est assez intéressant sur les thèmes de la continuation et de la monade.
La vidéo qui va avec cette présentation (slideshare) est en fait disponible à vimeo.
La partie Monade commence environ 37 minutes, sur cette vidéo d'une heure, et commence avec la diapositive 42 de sa présentation de diapositives 58.

Il est présenté comme "le modèle de conception principal pour la programmation fonctionnelle", mais le langage utilisé dans les exemples est Scala, qui est à la fois OOP et fonctionnel.
Vous pouvez lire plus sur Monad à Scala dans le blog "Monades - Une autre façon d'abstraire les calculs à Scala", de Debasish Ghosh (27 mars 2008).

Un type constructeur M est une monade si elle supporte ces opérations:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

Ainsi, par exemple (à Scala):

  • Option est une monade
    unité def [A] (x: A): Option [A] = Some (x)

    def flatMap [A, B] (m: Option [A]) (f: A => Option [B]): Option [B] =
      m correspond {
       cas Aucun => Aucun
       cas Quelques (x) => f (x)
      }
  • List est Monad
    unité def [A] (x: A): liste [A] = liste (x)

    def flatMap [A, B] (m: liste [A]) (f: A => liste [B]): liste [B] =
      m correspond {
        cas Nil => Nil
        cas x :: xs => f (x) ::: flatMap (xs) (f)
      }

Les monades sont une grosse affaire dans Scala en raison de la syntaxe pratique construite pour tirer parti des structures Monad:

for compréhension à Scala:

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

est traduit par le compilateur pour:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

L'abstraction clé est la flatMap, qui lie le calcul par chaînage.
Chaque invocation de flatMap renvoie le même type de structure de données (mais de valeur différente), qui sert d'entrée à la commande suivante dans la chaîne.

Dans l'extrait ci-dessus, flatMap prend en entrée une fermeture (SomeType) => List[AnotherType] et renvoie un List[AnotherType]. Le point important à noter est que tous les flatMaps prennent le même type de fermeture en entrée et retournent le même type en sortie.

C'est ce qui "lie" le thread de calcul - chaque élément de la séquence dans la for-comprehension doit honorer cette même contrainte de type.


Si vous prenez deux opérations (qui peuvent échouer) et passez le résultat à la troisième, comme:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

mais sans profiter de Monad, vous obtenez un code OOP alambiqué comme:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

alors qu'avec Monad, vous pouvez travailler avec les types réels (Venue, User) comme toutes les opérations fonctionnent, et gardez le truc de vérification d'Option caché, tout cela à cause des flatmaps de la syntaxe for:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

La partie rendement ne sera exécutée que si les trois fonctions ont Some[X]; tout None serait directement retourné à confirm.


Alors:

Les monades permettent le calcul ordonné dans la programmation fonctionnelle, ce qui nous permet de modéliser le séquençage des actions dans une forme structurée agréable, un peu comme un DSL.

Et la plus grande puissance vient avec la capacité de composer des monades qui servent des buts différents, dans des abstractions extensible dans une application.

Ce séquençage et le filetage des actions par une monade est fait par le compilateur de langage qui fait la transformation à travers la magie des fermetures.


En passant, Monad n'est pas seulement un modèle de calcul utilisé dans FP: voir ceci article de blog.

La théorie des catégories propose de nombreux modèles de calcul. Parmi eux

  • le modèle de calcul de Arrow
  • le modèle de calcul de Monad
  • le modèle Applicatif de calculs

35
2018-06-06 04:14



J'ai écrit un court article comparant le code Python OOP standard au code Python monadique démontrant le processus de calcul sous-jacent avec des diagrammes. Il suppose aucune connaissance préalable de la PF. J'espère que vous le trouverez utile - http://nikgrozev.com/2013/12/10/monads-in-15-minutes/


28
2018-05-20 11:17



Pour respecter les lecteurs rapides, je commence par une définition précise d'abord, Continuez avec l'explication rapide "Anglais simplifié", puis passez aux exemples.

Voici une définition à la fois concise et précise légèrement reformulé:

UNE monade (en informatique) est formellement une carte qui:

  • envoie chaque type X d'un langage de programmation donné à un nouveau type T(X) (appelé le "type de T-computations avec des valeurs dans X");

  • équipé d'une règle pour composer deux fonctions de la forme    f:X->T(Y) et g:Y->T(Z) à une fonction g∘f:X->T(Z);

  • d'une manière qui est associative dans le sens évident et unitaire par rapport à une fonction d'unité donnée appelée pure_X:X->T(X), être considéré comme prenant une valeur au calcul pur qui renvoie simplement cette valeur.

Donc, en termes simples, un monade est un règle de passer de tout type X à un autre type T(X)et un règle de passer de deux fonctions f:X->T(Y) et g:Y->T(Z) (que vous voudriez composer mais ne peut pas) à une nouvelle fonction h:X->T(Z). Lequel, cependant, n'est pas la composition au sens mathématique strict. Nous sommes fondamentalement en train de «plier» la composition de la fonction ou de redéfinir la façon dont les fonctions sont composées.

De plus, nous avons besoin de la règle de composition de la monade pour satisfaire les axiomes mathématiques «évidents»:

  • Associativité: Composition f avec g puis avec h (de l'extérieur) devrait être le même que la composition g avec h puis avec f (de l'Intérieur).
  • Propriété unitaire: Composition f avec le identité la fonction de chaque côté devrait donner f.

Encore une fois, en termes simples, nous ne pouvons pas devenir fous redéfinir la composition de notre fonction comme nous l'aimons:

  • Nous avons d'abord besoin de l'associativité pour pouvoir composer plusieurs fonctions dans une rangée, par ex. f(g(h(k(x))), et ne pas se soucier de spécifier les paires de fonctions de composition d'ordre. Comme la règle de la monade ne prescrit que comment composer un paire de fonctionsSans cet axiome, nous aurions besoin de savoir quelle paire est composée en premier et ainsi de suite. (Notez que cela est différent de la propriété de commutativité f composé avec g étaient les mêmes que g composé avec f, ce qui n'est pas obligatoire).
  • Et deuxièmement, nous avons besoin de la propriété unitaire, qui est simplement de dire que les identités composent trivialement la façon dont nous les attendons. Nous pouvons donc refactoriser en toute sécurité les fonctions à chaque fois que ces identités peuvent être extraites.

Donc encore une fois en bref: Une monade est la règle de l'extension de type et des fonctions de composition satisfaisant les deux axiomes - associativité et propriété unitaire.

En termes pratiques, vous voulez que la monade soit implémentée pour vous par le langage, le compilateur ou le framework qui prendrait soin des fonctions de composition pour vous. Vous pouvez donc vous concentrer sur l'écriture de la logique de votre fonction plutôt que sur la façon dont leur exécution est implémentée.

C'est essentiellement cela, en un mot.


En tant que mathématicien professionnel, je préfère éviter d'appeler h la "composition" de f et g. Parce que mathématiquement, ça ne l'est pas. L'appeler la "composition" suppose à tort que h est la vraie composition mathématique, ce qu'elle n'est pas. Il n'est même pas uniquement déterminé par f et g. Au lieu de cela, c'est le résultat de la nouvelle «règle de composition» des fonctions de notre monade. Ce qui peut être totalement différent de la composition mathématique réelle même si celle-ci existe!


Monad est pas un foncteur! Un foncteur F est une règle pour aller de type X taper F(X) et fonctions (morphisme) entre les types X et Y aux fonctions entre F(X) et F(Y) (envoyer des objets à des objets et leurs morphismes à des morphismes dans la théorie des catégories). Au lieu de cela une monade envoie un paire des fonctions f et g à un nouveau h.


Pour le rendre moins sec, permettez-moi d'essayer de l'illustrer par l'exemple que j'annote avec de petites sections, de sorte que vous pouvez aller droit au but.

Exception jetant comme exemples Monad

Supposons que nous voulions composer deux fonctions:

f: x -> 1 / x
g: y -> 2 * y

Mais f(0) n'est pas défini, donc une exception e Est lancé. Alors comment pouvez-vous définir la valeur de composition g(f(0))? Jetez une exception encore une fois, bien sûr! Peut-être même e. Peut-être une nouvelle exception mise à jour e1.

Qu'est-ce qui se passe précisément ici? Premièrement, nous avons besoin de nouvelles valeurs d'exception (différentes ou identiques). Tu peux les appeler nothing ou null ou quoi que ce soit, mais l'essence reste la même - ils devraient être de nouvelles valeurs, par ex. ça ne devrait pas être un number dans notre exemple ici. Je préfère ne pas les appeler null pour éviter la confusion avec comment null peut être mis en œuvre dans une langue spécifique. De même je préfère éviter nothing parce qu'il est souvent associé à null, qui, en principe, est ce que null Cependant, ce principe devrait souvent être modifié pour toutes les raisons pratiques.

Qu'est-ce que l'exception exactement?

C'est une question triviale pour tout programmeur expérimenté, mais je voudrais laisser tomber quelques mots juste pour éteindre tout ver de confusion:

Exception est un objet encapsulant des informations sur la façon dont le résultat d'exécution invalide s'est produit.

Cela peut aller de jeter tous les détails et renvoyer une seule valeur globale (comme NaN ou null) ou générer une longue liste de journaux ou ce qui s'est exactement passé, l'envoyer à une base de données et la répliquer partout dans la couche de stockage de données distribuées;)

La différence importante entre ces deux exemples extrêmes d'exception est que dans le premier cas, il y a pas d'effets secondaires. Dans la seconde il y a. Ce qui nous amène à la question (mille dollars):

Les exceptions sont-elles autorisées dans les fonctions pures?

Réponse plus courte: Oui, mais seulement quand ils ne conduisent pas à des effets secondaires.

Réponse plus longue Pour être pur, la sortie de votre fonction doit être déterminée uniquement par son entrée. Nous modifions donc notre fonction f En envoyant 0 à la nouvelle valeur abstraite e que nous appelons l'exception. Nous nous assurons que cette valeur e ne contient aucune information extérieure qui ne soit pas uniquement déterminée par notre contribution, qui est x. Voici donc un exemple d'exception sans effet secondaire:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

Et voici un avec effet secondaire:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

En fait, cela n'a d'effets secondaires que si ce message peut éventuellement changer à l'avenir. Mais s'il est garanti de ne jamais changer, cette valeur devient uniquement prévisible, et donc il n'y a pas d'effet secondaire.

Pour le rendre encore plus idiot. Une fonction retournant 42 jamais est clairement pur. Mais si quelqu'un de fou décide de faire 42 une variable dont la valeur peut changer, la même fonction cesse d'être pure dans les nouvelles conditions.

Notez que j'utilise la notation littérale d'objet pour la simplicité pour démontrer l'essence. Malheureusement, les choses sont gâchées dans des langages comme JavaScript, où error n'est pas un type qui se comporte comme nous le voulons ici en ce qui concerne la composition des fonctions, alors que les types réels comme null ou NaN ne vous comportez pas de cette façon, mais optez plutôt pour des conversions de type artificiel et pas toujours intuitif.

Type d'extension

Comme nous voulons faire varier le message à l'intérieur de notre exception, nous déclarons vraiment un nouveau type E pour l'ensemble de l'objet d'exception, puis C'est ce que maybe number fait, en dehors de son nom confus, qui doit être de type number ou du nouveau type d'exception E, donc c'est vraiment l'union number | E de number et E. En particulier, cela dépend de la façon dont nous voulons construire E, qui n'est ni suggéré ni reflété dans le nom maybe number.

Qu'est-ce que la composition fonctionnelle?

C'est l'opération mathématique prenant des fonctions f: X -> Y et g: Y -> Z et la construction leur composition en tant que fonction h: X -> Z satisfaisant h(x) = g(f(x)). Le problème avec cette définition se produit lorsque le résultat f(x) n'est pas autorisé comme argument de g.

En mathématiques, ces fonctions ne peuvent être composées sans travail supplémentaire. La solution strictement mathématique pour notre exemple ci-dessus de f et g est d'enlever 0 de l'ensemble de la définition de f. Avec ce nouvel ensemble de définition (nouveau type plus restrictive de x), f devient composable avec g.

Cependant, il n'est pas très pratique dans la programmation de restreindre l'ensemble des définitions f comme ça. Au lieu de cela, des exceptions peuvent être utilisées.

Ou comme une autre approche, des valeurs artificielles sont créées comme NaN, undefined, null, Infinity etc. Donc vous évaluez 1/0 à Infinity et 1/-0 à -Infinity. Puis forcez la nouvelle valeur dans votre expression au lieu de lancer une exception. Conduire à des résultats que vous pouvez ou ne pas trouver prévisibles:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

Et nous sommes de retour à des numéros réguliers prêts à aller de l'avant;)

JavaScript nous permet de continuer à exécuter des expressions numériques à tout prix sans lancer d'erreurs comme dans l'exemple ci-dessus. Cela signifie qu'il permet également de composer des fonctions. Ce qui est exactement ce qui concerne monade - c'est une règle de composer des fonctions satisfaisant les axiomes tels que définis au début de cette réponse.

Mais la règle de la fonction de composition, découlant de la mise en œuvre de JavaScript pour traiter les erreurs numériques, est-elle une monade?

Pour répondre à cette question, tout ce dont vous avez besoin est de vérifier les axiomes (à gauche comme exercice ne faisant pas partie de la question ici).

L'exception de lancer peut-elle être utilisée pour construire une monade?

En effet, une monade plus utile serait plutôt la règle prescrivant que si f jette une exception pour certains x, ainsi que sa composition avec tout g. Plus faire l'exception E globalement unique avec une seule valeur possible (objet terminal en théorie des catégories). Maintenant, les deux axiomes sont immédiatement vérifiables et nous obtenons une monade très utile. Et le résultat est ce qui est bien connu comme le peut-être monade.


21
2018-04-24 19:37



Une monade est un type de données qui encapsule une valeur et à laquelle, essentiellement, deux opérations peuvent être appliquées:

  • return x crée une valeur du type monad qui encapsule x
  • m >>= f (lisez-le comme "l'opérateur bind") applique la fonction f à la valeur de la monade m

C'est ce qu'est une monade. Il y a quelques techniques de plus, mais fondamentalement ces deux opérations définissent une monade. La vraie question est, "Quelle monade Est-ce que», et cela dépend de la monade - les listes sont des monades, les Maybes sont des monades, les opérations des OI sont des monades, tout ce que cela signifie quand nous disons que ces choses sont des monades, c'est qu'elles ont l'interface de la monade. return et >>=.


18
2018-04-24 13:45



De Wikipédia:

En programmation fonctionnelle, une monade est   une sorte de type de données abstrait utilisé pour   représenter les calculs (au lieu de   données dans le modèle de domaine). Monades   permettre au programmeur de chaîner des actions   ensemble pour construire un pipeline, dans lequel   chaque action est décorée avec   règles de traitement supplémentaires fournies   par la monade. Programmes écrits en   style fonctionnel peut faire usage de   monades pour structurer les procédures   inclure des opérations séquencées,1[2]   ou pour définir des flux de contrôle arbitraires   (comme la gestion de la concurrence,   continuations, ou exceptions).

Formellement, une monade est construite par   définir deux opérations (lier et   return) et un constructeur de type M   doit remplir plusieurs propriétés à   permettre la bonne composition de   fonctions monadiques (c'est-à-dire des fonctions qui   utiliser les valeurs de la monade comme leur   arguments). L'opération de retour prend   une valeur d'un type simple et le met   dans un récipient monadique de type M.   L'opération de liaison effectue la   processus inverse, extraire le   valeur originale du conteneur et   le transmettre à la prochaine associée   fonctionner dans le pipeline.

Un programmeur composera monadic   fonctions pour définir un traitement de données   pipeline. La monade agit comme un   cadre, car c'est un comportement réutilisable   qui décide de l'ordre dans lequel le   fonctions monadiques spécifiques dans le   pipeline sont appelés, et gère tous   le travail d'infiltration requis par le   calcul. [3] La liaison et le retour   opérateurs entrelacés dans le pipeline   sera exécuté après chaque monadique   la fonction retourne le contrôle, et   prendre soin des aspects particuliers   géré par la monade.

Je crois que cela l'explique très bien.


10
2017-12-01 03:58