Je suis venu à ce poste pour mieux comprendre l'inférence de la citation infâme de la théorie des catégories de Mac Lane pour le mathématicien de travail .
Pour décrire ce qu'est quelque chose, il est souvent tout aussi utile de décrire ce qu'il n'est pas.
Le fait que Mac Lane utilise la description pour décrire une monade, cela pourrait impliquer qu'elle décrit quelque chose d'unique aux monades. Restez avec moi. Pour développer une compréhension plus large de la déclaration, je pense qu'il doit être clair qu'il ne décrit pas quelque chose qui est unique aux monades; la déclaration décrit également Applicative et Flèches entre 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 aux similitudes.
Monade et Applicative répondent aux critères:
L'instruction utilise "Category of ..." Ceci définit la portée de l'instruction. À titre d'exemple, la catégorie Functor décrit la portée de f * -> g *
, c'est-à-dire Any functor -> Any functor
, par exemple, Tree * -> List *
ou Tree * -> Tree *
.
Ce qu'un énoncé catégorique ne précise 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
. Alors que mon imagination passe à 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 rassemble comme suit:
- portée du foncteur
:: f a -> g b
(c.-à-d. n'importe quel type paramétré vers n'importe quel type paramétré)
- endo + functor
:: f a -> f b
(c'est-à-dire n'importe quel type paramétré pour le même type paramétré) ... dit différemment,
- un monoïde dans la catégorie des endofoncteurs
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
), ne parviennent pas à illustrer que je suis autorisé à utiliser une flèche paramétrée avec un certain nombre de valeurs monoïdes, à partir d' un objet de type autorisé dans Monoid. La définition de l'équivalence de la flèche d'identité endo, ~ ignore la valeur de type du foncteur ainsi que le type et la valeur de la couche la plus interne de "charge utile". Ainsi, l'équivalence revient true
dans toute situation où les types fonctoriels correspondent (par exemple, Nothing -> Just * -> Nothing
est équivalent à Just * -> Just * -> Just *
parce qu'ils sont les deux Maybe -> Maybe -> Maybe
).
Encadré: ~ l'extérieur est conceptuel, mais est le symbole le plus à gauche f a
. Il décrit également ce que "Haskell" lit en premier (vue d'ensemble); donc Type est "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 à relier dans Category. La catégorie d'ensemble est utilisée pour décrire les types (int, chaînes, peut-être int, etc.) qui inclut la catégorie de foncteur (types paramétrés). La chaîne de référence: type de foncteur, valeurs du foncteur (éléments de l'ensemble de ce foncteur, par exemple, rien, juste), et à son tour, tout le reste auquel chaque valeur du foncteur pointe. Dans la catégorie, la relation est décrite différemment, par exemple, return :: a -> m a
est considérée comme une transformation naturelle d'un Functor à un autre Functor, différente de tout ce qui a été mentionné jusqu'à présent.
Revenant au fil principal, 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:
- à l'extérieur, il apparaît comme un seul objet (par exemple,
:: List
); statique
- mais à l'intérieur, permet beaucoup de dynamique
- n'importe quel nombre de valeurs du même type (par exemple, Empty | ~ NonEmpty) comme fourrage à des fonctions de n'importe quelle arité. Le produit tensoriel réduira n'importe quel nombre d'entrées à une seule valeur ... pour la couche externe (~
fold
qui ne dit rien sur la charge utile)
- gamme infinie de fois le type et les valeurs pour la couche la plus intérieure
À 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 déterminer s'il faut créer du code avec un contexte partagé pour prendre en charge des calculs qui dépendent les uns des autres, par rapport aux calculs qui peuvent être exécutés en parallèle, cette infâme déclaration, avec autant qu'elle décrit, n'est 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 à prendre, la déclaration est sans objet.
C'est souvent mal compris. La déclaration continue en décrivant join :: m (m a) -> m a
comme le produit tensoriel pour l'endofoncteur monoïdal. Cependant, il ne précise 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 de combinaison des valeurs est exactement la même; la même entrée génère la même sortie de chacun (contrairement aux monoïdes Sum et Product pour Int car ils génèrent des résultats différents lors de la combinaison 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 les (>>=)
deux fournissent un accès simultané aux deux m
valeurs afin de calculer la valeur de retour unique. La logique utilisée pour calculer la valeur de retour est exactement la même. S'il n'y avait pas les différentes formes des fonctions qu'ils paramètrent ( f :: a -> b
contre k :: a -> m b
) et la position du paramètre avec le même type de retour du calcul (c'est-à-dire a -> b -> b
contre b -> a -> b
pour chacun respectivement), je soupçonne que nous aurions pu paramétrer la logique monoïdale, la produit tensoriel, à réutiliser dans les deux définitions. Comme exercice pour faire le point, essayez de mettre en œuvre ~t
, et vous vous retrouvez avec (<*>)
et (>>=)
selon la façon dont vous décidez de le définir forall a b
.
Si mon dernier point est au minimum conceptuellement vrai, il explique alors la différence précise et unique de calcul entre Applicative et Monad: les fonctions qu'ils paramètrent. En d'autres termes, la différence est externe à l'implémentation de ces classes de type.
En conclusion, dans ma propre expérience, la citation infâme de Mac Lane a fourni un grand meme "goto", un poteau indicateur pour moi de référence tout en naviguant dans la catégorie pour mieux comprendre les idiomes utilisés dans Haskell. Il réussit à saisir l'étendue d'une puissante capacité de calcul rendue merveilleusement accessible dans Haskell.
Cependant, il y a de l'ironie dans la façon dont j'ai d'abord mal compris l'applicabilité de la déclaration en dehors de la monade, et ce que j'espère véhiculé 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'il ne dit pas, c'est précisément la petite mais utile distinction entre eux.
- E