Mise à JOUR: Cette question a fait l'objet d'une immense longue série de blog, vous pouvez lire les Monades -- merci pour la grande question!
Dans les termes d'une OOP programmeur de le comprendre (sans aucune programmation fonctionnelle en arrière-plan), ce qui est une monade?
Une monade est un "amplificateur" de types qui obéit à certaines règles et qui a certaines opérations prévues.
Tout d'abord, qu'est ce qu'un "amplificateur de types"? Je veux dire par là un système qui vous permet de prendre un type et de le transformer en un plus d'un type spécial. Par exemple, en C# envisager Nullable<T>
. C'est un amplificateur de types. Il vous permet de prendre un type, disons int
, et d'ajouter une nouvelle fonctionnalité à ce type, à savoir, que maintenant, il peut être null quand il ne pouvait pas avant.
Comme deuxième exemple, envisager IEnumerable<T>
. C'est un amplificateur de types. Il vous permet de prendre un type, disons, string
, et d'ajouter une nouvelle fonctionnalité à ce type, à savoir, que vous pouvez désormais effectuer une séquence de chaînes de n'importe quel nombre de cordes simples.
Quels sont les "certaines règles"? Brièvement, qu'il y est un moyen judicieux pour les fonctions sur le type sous-jacent à travailler sur le amplifié type tels qu'ils suivent les règles normales de la fonctionnelle de la composition. Par exemple, si vous avez une fonction sur les entiers, dire
int M(int x) { return x + N(x * 2); }
ensuite, la fonction correspondante sur Nullable<int>
peut faire tous les opérateurs et les appels à y travailler ensemble "de la même manière qu'ils le faisaient avant.
(Qui est extrêmement vague et imprécise; vous avez demandé une explication qui n'a pas supposer quoi que ce soit à propos de la connaissance de la fonctionnelle de composition).
Quels sont les "opérations"?
- Qu'il y est une façon de prendre une valeur d'une amplification de type et de le transformer en une valeur de l'amplification de type.
- Qu'il existe un moyen de transformer les opérations sur l'amplification de type opérations sur le amplifié type qui obéit aux règles de la fonctionnelle de la composition mentionné avant
- Qu'il est généralement un moyen pour obtenir l'amplification de type de retour de la amplifié type. (Ce dernier point n'est pas strictement nécessaire pour une monade, mais c'est souvent le cas qu'une telle opération existe.)
Encore une fois, prendre en Nullable<T>
comme un exemple. Vous pouvez transformer un int en Nullable<int>
avec le constructeur. Le compilateur C# prend soin de la plupart des nullable "lifting" pour vous, mais si ce n'est pas le cas, la levée de la transformation est simple: une opération, dire,
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 nullable int de retour dans un int est fait avec la Valeur de la propriété.
C'est la transformation de la fonction qui est la clé de bits. Remarquez comment le réel de la sémantique de la nullable opération -- qu'une opération sur une valeur null propage l'null -- est capturé dans la transformation. On peut la généraliser. Supposons que vous disposez d'une fonction de type int int, comme l'origine, notre M. Vous pouvez facilement faire une fonction qui prend un entier et retourne un Nullable<int>
parce que vous pouvez simplement exécuter le résultat par le biais de la nullable constructeur. Maintenant, supposons que vous avez cet accroissement de la méthode de l'ordre:
Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
if (amplified == null)
return null;
else
return func(amplified.Value);
}
Voir ce que vous pouvez faire avec cela? Toute méthode qui prend un entier et retourne un int, ou prend un entier et retourne un nullable int pouvez maintenant avoir la nullable de la sémantique appliquée.
En outre: supposons que vous avez deux méthodes
Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }
et vous souhaitez composer eux:
Nullable<int> Z(int s) { return X(Y(s)); }
C'est, Z est la composition de X et Y. Mais vous ne pouvez pas le faire parce que X prend un int, et Y retourne nullable int. Mais puisque vous avez le "bind" de l'opération, 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 rend la composition de fonctions sur amplifiée types de travail. Les "règles" je handwaved de parler ci-dessus sont que la monade conserve les règles de la fonction normale de la composition; que la composition de l'identité des fonctions de résultats dans la fonction d'origine, que la composition est associative, et ainsi de suite.
En C#, "Bind" est appelé "SelectMany". Jetez un oeil à la façon dont il fonctionne sur la séquence de la monade. Nous avons besoin de trois choses: la transformation d'une valeur dans une séquence, transformer une séquence en une valeur, et de lier les opérations sur les séquences. Ces opérations sont:
IEnumerable<T> MakeSequence<T>(T item)
{
yield return item;
}
T Single<T>(IEnumerable<T> sequence)
{
// let's just take the first one
foreach(T item in sequence) return item;
}
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;
}
Le nullable monade règle était "pour combiner les deux fonctions qui produisent nullable ensemble, vérifier pour voir si l'intérieur de l'un des résultats null; si ce n'est, à produire de la valeur null si elle n'est pas, alors appelez de l'extérieur du résultat". C'est le désiré de la sémantique des valeurs null. La séquence de l'errance de la règle est de "combiner les deux fonctions qui produisent des séquences, appliquer l'extérieur de la fonction à chaque élément produit par la fonction interne, et puis concaténer toutes les séquences obtenues ensemble". Les fondamentaux de la sémantique des monades sont capturés dans le Bind/SelectMany méthodes; c'est la méthode qui vous dit que la monade vraiment les moyens.
Nous pouvons faire encore mieux. Supposons que vous avez une des séquences d'entiers, et une méthode qui prend ints et les résultats dans des séquences de chaînes de caractères. On peut généraliser l'opération de liaison pour permettre la composition de fonctions et différents retour amplifié types, aussi longtemps que les entrées de un match les sorties de l'autre:
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 "amplification de ce groupe de personne entiers dans une séquence d'entiers. Transformer ce nombre entier en un bouquet de chaînes, amplifié à une séquence de chaînes de caractères. Maintenant, peut mettre à la fois l'ensemble des opérations: compléter cet ensemble d'entiers dans la concaténation de toutes les séquences de chaînes." Les monades vous permettent de composer votre amplifications.
Quel problème à résoudre et quels sont les endroits les plus communs, il est utilisé?
C'est un peu comme demander "quels sont les problèmes liés à un singleton résoudre?", mais je vais donner un coup de feu.
Les monades sont généralement utilisés pour résoudre des problèmes tels que:
- J'ai besoin de faire de nouvelles capacités pour ce type et encore combiner les anciennes fonctions de ce type à utiliser les nouvelles fonctionnalités.
- J'ai besoin de saisir un tas d'opérations sur les types et les représentent ces opérations comme composable objets, la construction de plus en plus grandes compositions jusqu'à ce que j'ai juste le droit de la série d'opérations a représenté, et puis j'ai besoin pour commencer à obtenir des résultats de la chose
- J'ai besoin de représenter côté-effectuer les opérations proprement dans une langue qui déteste les effets secondaires
(À noter que ce sont essentiellement trois façons de dire la même chose.)
C# utilise les monades dans sa conception. Comme déjà mentionné, la nullable modèle est très proche de la "monade". LINQ est entièrement construit à partir de monades; le "SelectMany" la méthode est quoi la sémantique travail de composition de l'exploitation. (Erik Meijer est friand de souligner que chaque LINQ fonction pourrait effectivement être mis en œuvre par SelectMany; tout le reste est juste une commodité.)
Afin de clarifier la nature de la compréhension que je cherchais, disons que vous avez été la conversion d'un FP demande monades en programmation orientée objet application. Que feriez-vous pour port les responsabilités des monades dans la programmation orientée objet application?
La plupart de la programmation orientée objet les langues n'ont pas assez riche système de type pour représenter la monade modèle lui-même directement; vous avez besoin d'un type de système qui prend en charge les types qui sont plus élevés types de types génériques. Je ne voudrais pas essayer de le faire. Plutôt, je voudrais mettre en œuvre les types génériques qui représentent chaque monade, et de mettre en œuvre des méthodes qui représentent les trois opérations dont vous avez besoin: tournage d'une valeur dans un amplifié valeur, la transformation d'un amplifié valeur en une valeur, et la transformation d'une fonction d'amplification des valeurs dans une fonction amplifié valeurs.
Un bon endroit pour commencer est de savoir comment nous avons mis en place LINQ en C#. L'étude de la SelectMany méthode; elle est la clé pour comprendre comment la séquence monade travaille en C#. C'est une méthode très simple, mais très puissant!
Pour un plus en profondeur et, théoriquement, son explication de monades en C#, je vous recommande vivement mon collègue de Wes Dyer article sur le sujet. Cet article est ce qui explique les monades à moi quand ils ont enfin "cliqué" pour moi.
Les Merveilles de Monades