Pour respecter les lecteurs rapides, je commence par une définition précise en premier, je continue par une explication rapide plus "en anglais simple", puis je passe aux exemples.
Voici une définition à la fois concise et précise légèrement reformulée:
Une monade (en informatique) est officiellement une carte qui:
envoie chaque type Xd'un langage de programmation donné à un nouveau type T(X)(appelé "type de- Tcalculs 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 au sens évident et unitaire par rapport à une fonction unitaire donnée appelée pure_X:X->T(X), à considérer comme prenant une valeur au calcul pur qui renvoie simplement cette valeur.
Donc, en termes simples, une monade est une règle à passer de n'importe quel type Xà un autre typeT(X) , et une règle à passer de deux fonctions f:X->T(Y)et g:Y->T(Z)(que vous souhaitez composer mais ne pouvez pas) à une nouvelle fonctionh:X->T(Z) . Mais ce n'est pas la composition au sens mathématique strict. Nous sommes fondamentalement en train de «plier» la composition d'une 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é : Composer
favec gpuis avec h(de l'extérieur) doit être identique à composer gavec hpuis avec f(de l'intérieur).
- Propriété unitaire : Composer
favec la fonction d' identité de chaque côté devrait donner f.
Encore une fois, en termes simples, nous ne pouvons pas simplement devenir fous en redéfinissant notre composition de fonctions comme nous le souhaitons:
- Nous avons d'abord besoin de l'associativité pour pouvoir composer plusieurs fonctions d'affilée par exemple
f(g(h(k(x))), et ne pas nous soucier de spécifier l'ordre de paires de fonctions de composition. Comme la règle de la monade ne prescrit que comment composer une paire de fonctions , sans cet axiome, nous aurions besoin de savoir quelle paire est composée en premier et ainsi de suite. (Notez que c'est différent de la propriété de commutativité qui fcomposait avec gétait la même que gcomposée avec f, ce qui n'est pas requis).
- Et deuxièmement, nous avons besoin de la propriété unitaire, c'est-à-dire simplement que les identités composent trivialement comme nous les attendons. Nous pouvons donc refactoriser les fonctions en toute sécurité chaque fois que ces identités peuvent être extraites.
Donc encore une fois: 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.
Concrètement, vous souhaitez que la monade soit implémentée pour vous par le langage, le compilateur ou le framework qui se chargerait de composer des fonctions pour vous. Vous pouvez donc vous concentrer sur l'écriture de la logique de votre fonction plutôt que de vous soucier de la façon dont leur exécution est implémentée.
C'est essentiellement cela, en un mot.
Étant mathématicien professionnel, je préfère éviter d'appeler hla "composition" de fet g. Parce que mathématiquement, ce n'est pas le cas. L'appeler la "composition" suppose à tort que hc'est la vraie composition mathématique, ce qui n'est pas le cas. Il n'est même pas déterminé de manière unique par fet g. Au lieu de cela, c'est le résultat de la nouvelle "règle de composition" de notre monade. Ce qui peut être totalement différent de la composition mathématique réelle même si celle-ci existe!
Pour le rendre moins sec, permettez-moi d'essayer de l'illustrer par l'exemple que j'annote avec de petites sections, afin que vous puissiez aller droit au but.
Lancer d'exception comme exemples Monad
Supposons que nous voulons composer deux fonctions:
f: x -> 1 / x
g: y -> 2 * y
Mais f(0)n'est pas défini, donc une exception eest levée. Alors, comment pouvez-vous définir la valeur de composition g(f(0))? Jetez à nouveau une exception, bien sûr! Peut-être la même chose e. Peut-être une nouvelle exception mise à jour e1.
Que se passe-t-il précisément ici? Tout d'abord, nous avons besoin de nouvelles valeurs d'exception (différentes ou identiques). Vous pouvez les appeler nothingou nullou quoi que ce soit mais l'essence reste la même - elles devraient être de nouvelles valeurs, par exemple, ce ne devrait pas être un numberdans notre exemple ici. Je préfère ne pas les appeler nullpour éviter toute confusion avec la façon de nullles implémenter dans une langue spécifique. De même, je préfère éviter nothingcar il est souvent associé à nullce qui, en principe, est ce qui nulldevrait faire, cependant, ce principe est souvent plié pour des raisons pratiques.
Qu'est-ce que l'exception exactement?
C'est une question banale pour tout programmeur expérimenté mais j'aimerais laisser tomber quelques mots juste pour éteindre tout ver de confusion:
L'exception est un objet encapsulant des informations sur la façon dont le résultat non valide de l'exécution s'est produit.
Cela peut aller de la suppression des détails et du retour d'une seule valeur globale (comme NaNou null) ou de la génération d'une longue liste de journaux ou de ce qui s'est exactement passé, de l'envoyer à une base de données et de 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 n'y a pas d'effets secondaires . Dans le second, il y en a. Ce qui nous amène à la question (en milliers de dollars):
Les exceptions sont-elles autorisées dans les fonctions pures?
Réponse plus courte : oui, mais uniquement lorsqu'ils n'entraînent pas d'effets secondaires.
Réponse plus longue. Pour être pure, la sortie de votre fonction doit être uniquement déterminée par son entrée. Nous modifions donc notre fonction fen envoyant 0à la nouvelle valeur abstraite eque nous appelons exception. Nous nous assurons que cette valeur ene contient aucune information extérieure qui n'est pas déterminée de manière unique par notre entrée, ce 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 il n'y a donc aucun effet secondaire.
Pour le rendre encore plus idiot. Une fonction qui revient 42toujours est clairement pure. Mais si quelqu'un de fou décide de faire 42une variable dont la valeur pourrait changer, la même fonction cesse d'être pure dans les nouvelles conditions.
Notez que j'utilise la notation littérale de l'objet pour plus de simplicité afin de démontrer l'essence. Malheureusement, les choses sont gâchées dans des langages comme JavaScript, où il errorn'y a 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 aiment nullou NaNne se comportent pas de cette façon mais passent plutôt par certains artificiels et pas toujours intuitifs conversions de type.
Extension de type
Comme nous voulons faire varier le message à l'intérieur de notre exception, nous déclarons vraiment un nouveau type Epour l'ensemble de l'objet d'exception, puis c'est ce que maybe numberfait, à part son nom déroutant, qui doit être de type numberou du nouveau type d'exception E, c'est donc vraiment l'union number | Ede numberet E. En particulier, cela dépend de la façon dont nous voulons construire E, ce qui n'est ni suggéré ni reflété dans le nom maybe number.
Qu'est-ce que la composition fonctionnelle?
Ce sont les fonctions de prise de fonctionnement mathématiques
f: X -> Yet g: Y -> Zet la construction de leur composition en fonction h: X -> Zsatisfaisant 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 pas être composées sans travail supplémentaire. La solution strictement mathématique pour notre exemple ci-dessus de fet gest de supprimer 0de l'ensemble de définition de f. Avec ce nouvel ensemble de définition (nouveau type plus restrictif de x), fdevient composable avec g.
Cependant, il n'est pas très pratique en programmation de restreindre l'ensemble de définition de ce fgenre. Au lieu de cela, des exceptions peuvent être utilisées.
Ou comme une autre approche, les valeurs artificielles sont créées comme NaN, undefined, null, Infinityetc. Vous évaluez 1/0à Infinityet 1/-0à -Infinity. Et puis forcez la nouvelle valeur dans votre expression au lieu de lever l'exception. Menant à des résultats que vous pouvez ou non 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 à passer;)
JavaScript nous permet de continuer à exécuter des expressions numériques à tout prix sans générer d'erreurs comme dans l'exemple ci-dessus. Cela signifie qu'il permet également de composer des fonctions. C'est exactement de cela qu'il s'agit - 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 l'implémentation de JavaScript pour traiter les erreurs numériques, est-elle une monade?
Pour répondre à cette question, il vous suffit de vérifier les axiomes (laissés en exercice comme ne faisant pas partie de la question ici;).
L'exception de lancement peut-elle être utilisée pour construire une monade?
En effet, une monade plus utile serait plutôt la règle prescrivant que si flève une exception pour certains x, sa composition en fait de même g. De plus, l'exception est Eunique au monde avec une seule valeur possible ( objet terminal dans la théorie des catégories). Maintenant, les deux axiomes sont vérifiables instantanément et nous obtenons une monade très utile. Et le résultat est ce qui est bien connu comme la monade peut - être .