Qui a dit le premier
Une monade est juste un monoïde dans la catégorie des endofoncteurs, quel est le problème?
Et sur une note moins importante, est-ce vrai et si oui, pourriez-vous donner une explication (heureusement compréhensible pour quelqu'un qui n'a pas beaucoup d'expérience en Haskell)?
Ce phrasé particulier est de James Iry, tiré de son histoire fort divertissante Histoire brève, incomplète et la plupart du temps fausse des langages de programmation, dans laquelle il l'attribue de manière fictive à Philip Wadler.
La citation originale provient de Saunders Mac Lane dans des catégories pour le mathématicien actif , l'un des textes fondateurs de la théorie des catégories. Le voici dans son contexte , qui est probablement le meilleur endroit pour apprendre exactement ce que cela signifie.
Mais, je vais prendre un coup de poignard. La phrase originale est la suivante:
Au total, une monade en X n’est qu’un monoïde de la catégorie des endofoncteurs de X, le produit × étant remplacé par la composition des endofuncteurs et l’unité définie par l’endofuncteur d’identité.
X voici une catégorie. Les endofoncteurs sont des foncteurs d'une catégorie à elle-même (ce qui est généralement tous Functor
en ce qui concerne les programmeurs fonctionnels, car ils ne traitent généralement que d'une catégorie, la catégorie de types - mais je m'égare). Mais vous pouvez imaginer une autre catégorie qui est la catégorie des "endofoncteurs sur X". C'est une catégorie dans laquelle les objets sont des endofoncteurs et les morphismes sont des transformations naturelles.
Et parmi ces endofoncteurs, certains pourraient être des monades. Lesquelles sont des monades? Exactement ceux qui sont monoïdaux dans un sens particulier. Au lieu de préciser le mappage exact des monades en monoïdes (Mac Lane le faisant mieux que ce que je pouvais espérer), je vais simplement mettre leurs définitions respectives côte à côte et vous permettre de comparer:
* -> *
avec une instance Functor
)join
en Haskell)return
en Haskell)Avec un peu de strabisme, vous pourrez peut-être voir que ces deux définitions sont des instances du même concept abstrait .
Intuitivement, je pense que le vocabulaire mathématique sophistiqué dit ceci:
Un monoïde est un ensemble d'objets et une méthode pour les combiner. Les monoïdes bien connus sont:
Il y a aussi des exemples plus complexes.
En outre, chaque monoïde a un identité, qui est cet élément "no-op" qui n'a aucun effet lorsque vous le combinez avec autre chose:
Enfin, un monoïde doit être associatif. (vous pouvez réduire une longue chaîne de combinaisons comme vous le souhaitez, tant que vous ne modifiez pas l'ordre des objets de gauche à droite) L'addition est correcte ((5 + 3) +1 == 5+ (3+ 1)), mais la soustraction n'est pas ((5-3) -1! = 5-1)).
Maintenant, considérons un type spécial de jeu et une manière spéciale de combiner des objets.
Supposons que votre ensemble contienne des objets d'un type spécial: --- (fonctions. Et ces fonctions ont une signature intéressante: elles ne portent pas de chiffres en chiffres, ni de chaînes en chaînes. Au lieu de cela, chaque fonction porte un numéro sur une liste de numéros au cours d'un processus en deux étapes.
Exemples:
De plus, notre façon de combiner des fonctions est particulière. Un moyen simple de combiner une fonction est la composition : Prenons nos exemples ci-dessus et composons chaque fonction avec elle-même:
Sans entrer trop dans la théorie des types, le fait est que vous pouvez combiner deux entiers pour obtenir un entier, mais vous ne pouvez pas toujours composer deux fonctions et obtenir une fonction du même type. (Fonctions avec le type a -> a composera, mais a-> [a] ne sera pas)
Définissons donc une manière différente de combiner des fonctions. Lorsque nous combinons deux de ces fonctions, nous ne voulons pas "doubler" les résultats.
Voici ce que nous faisons. Lorsque nous voulons combiner deux fonctions F et G, nous suivons ce processus (appelé binding ):
Revenons à nos exemples, combinons (lient) une fonction avec elle-même en utilisant cette nouvelle façon de "lier" les fonctions:
Cette manière plus sophistiquée de combiner des fonctions est associative (ce qui découle du fait que la composition de fonctions est associative lorsque vous ne faites pas ce que vous voulez dans le wrapping).
Lier le tout ensemble,
Il y a beaucoup de façons de "boucler" les résultats. Vous pouvez créer une liste ou un ensemble ou ignorer tout sauf le premier résultat tout en notant s’il n’ya aucun résultat, joindre une sidecar d’état, imprimer un message de journal, etc., etc.
J'ai joué un peu avec les définitions dans l'espoir de faire passer l'idée essentielle de manière intuitive.
J'ai un peu simplifié les choses en insistant pour que notre monade opère sur des fonctions de type a -> [a] . En fait, les monades travaillent sur des fonctions de type a -> m b , mais la généralisation est une sorte de détail technique qui n’est pas l’idée principale.
Tout d'abord, les extensions et les bibliothèques que nous allons utiliser:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
Parmi ceux-ci, RankNTypes
est le seul qui soit absolument essentiel au-dessous. J'ai écrit une fois une explication de RankNTypes
que certaines personnes semblent avoir trouvé utile , je vais donc en parler.
Citant excellente réponse de Tom Crockett , nous avons:
Une monade est ...
- Un endofoncteur, T: X -> X
- Une transformation naturelle, μ: T × T -> T, où × signifie composition du foncteur
- Une transformation naturelle, η: I -> T, où I est l'identité endofoncteur sur X
... satisfaisant ces lois:
- µ (T × T) × T)) = µ (T × µ (T × T))
- μ (η (T)) = T = μ (T (η))
Comment traduisons-nous cela en code Haskell? Eh bien, commençons par la notion de transformation naturelle :
-- | A natural transformations between two 'Functor' instances. Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
Natural { eta :: forall x. f x -> g x }
Un type de la forme f :-> g
est analogue à un type de fonction, mais au lieu de le considérer comme un fonction entre deux types (de type *
), considérez-le comme un morphisme entre deux foncteurs (chaque de type * -> *
). Exemples:
listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
where go [] = Nothing
go (x:_) = Just x
maybeToList :: Maybe :-> []
maybeToList = Natural go
where go Nothing = []
go (Just x) = [x]
reverse' :: [] :-> []
reverse' = Natural reverse
Fondamentalement, dans Haskell, les transformations naturelles sont des fonctions d'un type f x
à un autre type g x
telles que la variable de type x
soit "inaccessible" à l'appelant. Ainsi, par exemple, sort :: Ord a => [a] -> [a]
ne peut pas être transformé en une transformation naturelle, car il est "pointilleux" sur les types que nous pouvons instancier pour a
. Une façon intuitive que j'utilise souvent pour y penser est la suivante:
Maintenant que cela est réglé, abordons les clauses de la définition.
La première clause est "un endofoncteur, T: X -> X." Eh bien, chaque Functor
dans Haskell est un endofoncteur dans ce que les gens appellent "la catégorie Hask", dont les objets sont des types Haskell (de type *
) et dont les morphismes sont des fonctions Haskell. Cela semble être une déclaration compliquée, mais en réalité très triviale. Cela signifie simplement qu'un Functor f :: * -> *
vous donne les moyens de construire un type f a :: *
pour tout a :: *
et une fonction fmap f :: f a -> f b
sur n'importe quel f :: a -> b
, et que ceux-ci obéissent aux lois du foncteur.
Deuxième clause: le foncteur Identity
de Haskell (qui est fourni avec la plate-forme, vous pouvez donc l'importer) est défini comme suit:
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
Ainsi, la transformation naturelle η: I -> T de la définition de Tom Crockett peut être écrite de cette façon pour tout Monad
instance t
:
return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)
Troisième clause: La composition de deux foncteurs dans Haskell peut être définie de cette façon (qui vient également avec la plate-forme):
newtype Compose f g a = Compose { getCompose :: f (g a) }
-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose fga) = Compose (fmap (fmap f) fga)
Ainsi, la transformation naturelle μ: T × T -> T de la définition de Tom Crockett peut être écrite comme suit:
join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)
L'affirmation qu'il s'agit d'un monoïde dans la catégorie des endofoncteurs signifie alors que Compose
(appliqué partiellement à ses deux premiers paramètres seulement) est associatif et que Identity
est son élément d'identité. C'est-à-dire que les isomorphismes suivants sont présents:
Compose f (Compose g h) ~= Compose (Compose f g) h
Compose f Identity ~= f
Compose Identity g ~= g
Celles-ci sont très faciles à prouver car Compose
et Identity
sont tous deux définis par newtype
, et les rapports Haskell définissent la sémantique de newtype
comme un isomorphisme entre le type défini et le type de l'argument du constructeur de données de la newtype
. Donc, par exemple, prouvons Compose f Identity ~= f
:
Compose f Identity a
~= f (Identity a) -- newtype Compose f g a = Compose (f (g a))
~= f a -- newtype Identity a = Identity a
Q.E.D.
Remarque: Non, ce n'est pas vrai. À un moment donné, Dan Piponi lui-même a commenté cette réponse en disant que la cause et l'effet étaient exactement le contraire, qu'il avait écrit son article en réponse à la remarque de James Iry. Mais il semble avoir été enlevé, peut-être par un compère compulsif.
Ci-dessous ma réponse originale.
Il est fort possible qu'Iry ait lu From Monoids to Monads , un article dans lequel Dan Piponi (sigfpe) dérive des monades à partir de monoïdes en Haskell, avec beaucoup de discussions sur la théorie des catégories et une mention explicite de "la catégorie des endofuncteurs". sur Hask ". Dans tous les cas, quiconque se demandant ce que signifie pour une monade d'être un monoïde dans la catégorie des endofoncteurs pourrait bénéficier de la lecture de cette dérivation.
Je suis venu à ce poste pour mieux comprendre l’inférence de la citation infâme de Mac Lane Théorie des catégories pour le mathématicien actif.
En décrivant ce qu'est quelque chose, il est souvent tout aussi utile de décrire ce que ce n'est pas.
Le fait que Mac Lane utilise la description pour décrire une monade pourrait signifier que celle-ci décrit quelque chose d'unique aux monades. Ours avec moi. Pour développer une compréhension plus large de la déclaration, je pense qu’il faut préciser qu’il décrit pas quelque chose qui est propre aux monades; la déclaration décrit également Applicative et Arrows parmi d'autres. Pour la même raison, nous pouvons avoir deux monoïdes sur Int (Sum et Product), nous pouvons avoir plusieurs monoïdes sur X dans la catégorie des endofoncteurs. Mais il y a encore plus de similitudes.
Monad et Applicative répondent aux critères:
(par exemple, au jour le jour Tree a -> List b
, mais dans la catégorie Tree -> List
)
Tree -> List
, seulement List -> List
.L'instruction utilise "Catégorie de ..." Ceci définit la portée de l'instruction. Par exemple, la catégorie de foncteur décrit la portée de f * -> g *
, c'est-à-dire Any functor -> Any functor
, par exemple, Tree * -> List *
ou Tree * -> Tree *
.
Ce qu'une déclaration catégorique ne spécifie pas décrit où tout et n'importe quoi est autorisé.
Dans ce cas, à l'intérieur des foncteurs, * -> *
aka a -> b
n'est pas spécifié, ce qui signifie Anything -> Anything including Anything else
. Lorsque mon imagination se déplace sur Int -> String, il inclut également Integer -> Maybe Int
ou même Maybe Double -> Either String Int
où a :: Maybe Double; b :: Either String Int
.
Donc, la déclaration se présente comme suit:
:: f a -> g b
(c'est-à-dire tout type paramétré à tout type paramétré):: f a -> f b
(c'est-à-dire tout type paramétré du même type paramétré) ... dit différemment,Alors, où est le pouvoir de cette construction? Pour apprécier toute la dynamique, j'avais besoin de voir que les dessins typiques d'un monoïde (objet unique avec ce qui ressemble à une flèche d'identité, :: single object -> single object
), n'illustrent pas le fait que je suis autorisé à utiliser une flèche paramétrée avec - nombre quelconque de valeurs monoïdes, à partir de l'objet de type n autorisé dans Monoid. Endo, ~ définition de la flèche d'identité de l'équivalence ignore du foncteur valeur de type et à la fois le type et la valeur de la couche la plus interne, "charge utile". Ainsi, l’équivalence renvoie true
dans toute situation dans laquelle les types de fonctions sont identiques (par exemple, Nothing -> Just * -> Nothing
est équivalent à Just * -> Just * -> Just *
car ils sont tous deux Maybe -> Maybe -> Maybe
).
Barre latérale: ~ extérieur est conceptuel, mais est le symbole le plus à gauche dans f a
. Il décrit également ce que "Haskell" lit en premier (vue d'ensemble); Le type est donc "extérieur" par rapport à une valeur de type. La relation entre les couches (une chaîne de références) en programmation n'est pas facile à mettre en relation dans la catégorie. La catégorie de jeu est utilisée pour décrire les types (Int, Strings, Maybe Int etc.) qui inclut la catégorie de foncteur (types paramétrés). La chaîne de référence: type de foncteur, valeurs de foncteur (éléments de l'ensemble de ce foncteur, par exemple, rien, tout simplement) et, à son tour, tout le reste sur la valeur de chaque foncteur. Dans la catégorie, la relation est décrite différemment, par exemple, return :: a -> m a
est considéré comme une transformation naturelle d'un foncteur à un autre, différent de tout ce qui a été mentionné jusqu'à présent.
Pour en revenir au fil conducteur, dans l’ensemble, pour tout produit tensoriel défini et une valeur neutre, l’énoncé finit par décrire une construction informatique étonnamment puissante née de sa structure paradoxale:
:: List
); statiquefold
qui ne dit rien sur la charge utile)En Haskell, il est important de clarifier l'applicabilité de la déclaration. La puissance et la polyvalence de cette construction n’ont absolument rien à voir avec une monade en soi. En d'autres termes, la construction ne repose pas sur ce qui rend une monade unique.
Lorsque vous essayez de savoir s'il faut construire du code avec un contexte partagé pour prendre en charge des calculs qui dépendent les uns des autres, par opposition à des calculs pouvant être exécutés en parallèle, cette déclaration infâme, avec autant qu'elle décrit, ne constitue pas un contraste entre le choix de Applicative, flèches et monades, mais est plutôt une description de combien ils sont les mêmes. Pour la décision en cause, la déclaration est sans objet.
Ceci est souvent mal compris. La déclaration décrit ensuite join :: m (m a) -> m a
comme le produit tenseur de l’endofoncteur monoïdal. Cependant, il n’explique pas comment, dans le contexte de cette déclaration, (<*>)
aurait également pu être choisi. C'est vraiment un exemple de six/demi douzaine. La logique pour combiner les valeurs est exactement la même; la même entrée génère la même sortie pour chacune (contrairement aux monoïdes Sum et Product pour Int car elles génèrent des résultats différents lors de la combinaison d'Ints).
Donc, pour récapituler: un monoïde dans la catégorie des endofoncteurs décrit:
~t :: m * -> m * -> m *
and a neutral value for m *
(<*>)
et (>>=)
offrent tous deux un accès simultané aux deux valeurs m
afin de calculer la valeur de retour unique. La logique utilisée pour calculer la valeur de retour est exactement la même. Si ce n’est pas pour les différentes formes des fonctions qu’elles paramètrent (f :: a -> b
versus k :: a -> m b
) et la position du paramètre avec le même type de retour du calcul (c.-à-d. a -> b -> b
versus b -> a -> b
pour chacun), je suppose que nous aurions pu paramétrer la logique monoïdale, le produit tensoriel, pour une utilisation ultérieure dans les deux définitions. En guise d’exercice, essayez d’implémenter ~t
et vous vous retrouverez avec (<*>)
et (>>=)
, selon la façon dont vous décidez de le définir forall a b
.
Si mon dernier point est au moins conceptuellement vrai, il explique ensuite la différence de calcul précise entre Applicative et Monad: les fonctions qu’ils paramètrent. En d'autres termes, la différence est externe à la mise en oeuvre de ces classes de types.
En conclusion, selon ma propre expérience, la citation tristement célèbre de Mac Lane fournissait un excellent "goto", un guide que je devais consulter tout en naviguant dans Category pour mieux comprendre les idiomes utilisés dans Haskell. Il réussit à saisir l'étendue d'une puissante capacité informatique rendue merveilleusement accessible en Haskell.
Cependant, il est ironique de constater que j'ai d'abord mal compris l'applicabilité de la déclaration en dehors de la monade et ce que j'espère transmis ici. Tout ce qu'il décrit s'avère être ce qui est similaire entre Applicative et Monads (et Arrows entre autres). Ce qu’elle ne dit pas, c’est précisément la distinction petite mais utile qui existe entre eux.
- E
Les réponses ici font un excellent travail en définissant à la fois les monoïdes et les monades, mais elles ne semblent toujours pas répondre à la question:
Et sur une note moins importante, est-ce vrai et si oui, pourriez-vous donner une explication (heureusement compréhensible pour quelqu'un qui n'a pas beaucoup d'expérience en Haskell)?
Le noeud de la matière qui manque ici est la notion différente de "monoïde", la soi-disant catégorisation , plus précisément - celle de monoïde dans une catégorie monoïdale. Malheureusement, le livre de Mac Lane lui-même le rend très déroutant :
Au total, une monade dans
X
est simplement un monoïde de la catégorie des endofoncteurs deX
, avec le produit×
remplacé par une composition d'endofuncteurs et d'unités définies par l'endofuncteur d'identité.
Pourquoi est-ce déroutant? Parce qu'il ne définit pas ce qui est "monoïde dans la catégorie des endofoncteurs" de X
. Au lieu de cela, cette phrase suggère de prendre un monoïde à l'intérieur de l'ensemble des endofoncteurs avec la composition du foncteur en tant qu'opération binaire et le foncteur d'identité en tant qu'unité monoïdale. Ce qui fonctionne parfaitement et transforme en monoïde tout sous-ensemble d’endofoncteurs contenant le foncteur identité et qui est fermé sous la composition d’un foncteur.
Pourtant, ce n’est pas la bonne interprétation, ce que le livre n’a pas précisé à ce stade. Une Monade f
est un endofuncteur fixé , et non un sous-ensemble d'endofuncteurs fermés sous composition. Une construction courante consiste à utiliser f
pour générer un monoïde en prenant l'ensemble de toutes les k
- compositions de plis f^k = f(f(...))
de f
avec lui-même, y compris k=0
qui correspond à l'identité f^0 = id
. Et maintenant, l'ensemble S
de tous ces pouvoirs pour tous k>=0
est bien un monoïde "avec le produit × remplacé par la composition d'endofuncteurs et l'unité définie par l'endofuncteur d'identité".
Et encore:
S
peut être défini pour tout foncteur f
ou même littéralement pour toute auto-carte de X
. C'est le monoïde généré par f
.S
donnée par la composition du foncteur et le foncteur d'identité n'a rien à voir avec f
être ou ne pas être une monade.Et pour rendre les choses plus confuses, la définition de "monoïde dans la catégorie monoïdale" vient plus tard dans le livre, comme vous pouvez le voir dans le table des matières . Et pourtant, comprendre cette notion est absolument essentiel pour comprendre le lien avec les monades.
Au chapitre VII sur les monoïdes (qui vient plus tard que le chapitre VI sur les monades), nous trouvons la définition de la catégorie dite monoïdale stricte comme triple (B, *, e)
, où B
est une catégorie, *: B x B-> B
un bifuncteur (foncteur par rapport à chaque composant avec un autre composant fixe) et e
est un objet unitaire dans B
, satisfaisant les lois d'associativité et d'unité:
(a * b) * c = a * (b * c)
a * e = e * a = a
pour tous les objets a,b,c
sur B
, et les mêmes identités pour tous les morphismes a,b,c
avec e
remplacés par id_e
, le morphisme d'identité de e
. Il est maintenant instructif de constater que, dans notre cas d’intérêt, où B
est la catégorie des endofoncteurs de X
avec des transformations naturelles en tant que morphismes, *
de la composition du foncteur et e
le foncteur d'identité, toutes ces lois sont satisfaites, ce qui peut être vérifié directement.
Ce qui suit dans le livre est la définition de la catégorie "décontractée" monoïdale , où les lois ne retiennent que modulo certaines transformations naturelles fixes satisfaisant ce que l'on appelle les relations de cohérence , ce qui n’est cependant pas important pour nos cas des catégories d’endofuncteur.
Enfin, dans la section 3 "Monoids" du chapitre VII, la définition actuelle est donnée:
Un monoïde
c
dans une catégorie monoïdale(B, *, e)
est un objet deB
avec deux flèches (morphismes)
mu: c * c -> c
nu: e -> c
faire 3 diagrammes commutatifs. Rappelons que, dans notre cas, il s’agit de morphismes appartenant à la catégorie des endofoncteurs, qui sont des transformations naturelles correspondant exactement à join
et return
pour une monade. La connexion devient encore plus claire lorsque nous rendons la composition *
plus explicite, en remplaçant c * c
par c^2
, où c
est notre monade.
Remarquez enfin que les 3 diagrammes commutatifs (dans la définition d'un monoïde dans la catégorie monoïdale) sont écrits pour des catégories monoïdales générales (non strictes), alors que dans notre cas, toutes les transformations naturelles découlant de la catégorie monoïdale sont en réalité des identités. Cela rendra les diagrammes exactement les mêmes que ceux de la définition d'une monade, rendant ainsi la correspondance complète.
En résumé, toute monade est par définition un endofuncteur, donc un objet de la catégorie des endofuncteurs, où les opérateurs monadiques join
et return
satisfont à la définition de un monoïde en ce que catégorie monoïdale particulière (stricte) . Inversement, tout monoïde de la catégorie monoïdale d’endofoncteurs est par définition un triple (c, mu, nu)
composé d’un objet et de deux flèches, par ex. transformations naturelles dans notre cas, satisfaisant les mêmes lois qu'une monade.
Enfin, notez la principale différence entre les monoïdes (classiques) et les monoïdes plus généraux dans les catégories monoïdales. Les deux flèches mu
et nu
ci-dessus ne sont plus une opération binaire et une unité d'un ensemble. Au lieu de cela, vous avez un endofoncteur fixe c
. La composition de foncteur *
et le foncteur d'identité ne fournissent pas à eux seuls la structure complète nécessaire à la monade, malgré cette remarque déroutante contenue dans le livre.
Une autre approche serait de comparer avec le monoïde standard C
de toutes les auto-cartes d'un ensemble A
, où l'opération binaire est la composition, qui peut être vue pour mapper le produit cartésien standard C x C
dans C
. En passant au monoïde catégorisé, nous remplaçons le produit cartésien x
par la composition du foncteur *
, et l’opération binaire est remplacée par la transformation naturelle mu
de c * c
à c
, c'est une collection des opérateurs join
join: c(c(T))->c(T)
pour chaque objet T
(saisissez la programmation). Et les éléments d'identité dans les monoïdes classiques, qui peuvent être identifiés avec des images de cartes d'un ensemble fixe à un point, sont remplacés par la collection des opérateurs return
return: T->c(T)
Mais maintenant, il n'y a plus de produits cartésiens, donc pas de paires d'éléments et donc pas d'opérations binaires.