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 X
d'un langage de programmation donné à un nouveau type T(X)
(appelé "type de- T
calculs 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
f
avec g
puis avec h
(de l'extérieur) doit être identique à composer g
avec h
puis avec f
(de l'intérieur).
- Propriété unitaire : Composer
f
avec 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 f
composait avec g
était la même que g
composé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 h
la "composition" de f
et g
. Parce que mathématiquement, ce n'est pas le cas. L'appeler la "composition" suppose à tort que h
c'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 f
et 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 e
est 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 nothing
ou null
ou 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 number
dans notre exemple ici. Je préfère ne pas les appeler null
pour éviter toute confusion avec la façon de null
les implémenter dans une langue spécifique. De même, je préfère éviter nothing
car il est souvent associé à null
ce qui, en principe, est ce qui null
devrait 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 NaN
ou 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 f
en envoyant 0
à la nouvelle valeur abstraite e
que nous appelons exception. Nous nous assurons que cette valeur e
ne 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 42
toujours est clairement pure. Mais si quelqu'un de fou décide de faire 42
une 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 error
n'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 null
ou NaN
ne 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 E
pour l'ensemble de l'objet d'exception, puis c'est ce que maybe number
fait, à part son nom déroutant, qui doit être de type number
ou du nouveau type d'exception E
, c'est donc vraiment l'union number | E
de number
et 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 -> Y
et g: Y -> Z
et la construction de leur composition en 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 pas être composées sans travail supplémentaire. La solution strictement mathématique pour notre exemple ci-dessus de f
et g
est de supprimer 0
de l'ensemble de définition de f
. Avec ce nouvel ensemble de définition (nouveau type plus restrictif de x
), f
devient composable avec g
.
Cependant, il n'est pas très pratique en programmation de restreindre l'ensemble de définition de ce f
genre. 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
, Infinity
etc. Vous évaluez 1/0
à Infinity
et 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 f
lève une exception pour certains x
, sa composition en fait de même g
. De plus, l'exception est E
unique 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 .