Je voudrais proposer une approche plus systématique pour répondre à cette question, et aussi pour montrer des exemples qui n'utilisent aucune astuce spéciale comme les valeurs "inférieures" ou les types de données infinis ou quelque chose comme ça.
Quand les constructeurs de types ne parviennent-ils pas à avoir des instances de classe de type?
En général, il existe deux raisons pour lesquelles un constructeur de type ne parvient pas à avoir une instance d'une certaine classe de type:
- Impossible d'implémenter les signatures de type des méthodes requises à partir de la classe de type.
- Peut implémenter les signatures de type mais ne peut pas satisfaire aux lois requises.
Les exemples du premier type sont plus faciles que ceux du deuxième type car pour le premier type, il suffit de vérifier si l'on peut implémenter une fonction avec une signature de type donnée, tandis que pour le second type, nous devons prouver qu'aucune implémentation pourrait éventuellement satisfaire aux lois.
Exemples spécifiques
Il s'agit d'un contrafoncteur, pas d'un foncteur, par rapport au paramètre type a
, car a
dans une position contravariante. Il est impossible d'implémenter une fonction avec signature de type (a -> b) -> F z a -> F z b
.
Un constructeur de type qui n'est pas un foncteur légal même si la signature de type de fmap
peut être implémentée:
data Q a = Q(a -> Int, a)
fmap :: (a -> b) -> Q a -> Q b
fmap f (Q(g, x)) = Q(\_ -> g x, f x) -- this fails the functor laws!
L'aspect curieux de cet exemple est que nous pouvons implémenter fmap
le bon type même s'il F
ne peut pas être un foncteur car il utilise a
dans une position contravariante. Donc, cette implémentation de celle fmap
montrée ci-dessus est trompeuse - même si elle a la signature de type correcte (je crois que c'est la seule implémentation possible de cette signature de type), les lois du foncteur ne sont pas satisfaites. Par exemple, fmap id
≠ id
, parce que let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
est 123
, mais let (Q(f,_)) = id (Q(read,"123")) in f "456"
est 456
.
En fait, F
c'est seulement un profuncteur, - ce n'est ni un foncteur ni un contrefoncteur.
Un foncteur légal qui n'est pas applicatif car la signature de type de pure
ne peut pas être implémentée: prenez la monade Writer (a, w)
et supprimez la contrainte qui w
devrait être un monoïde. Il est alors impossible de construire une valeur de type (a, w)
sur a
.
Un foncteur qui n'est pas applicative car la signature de type de <*>
ne peut pas être mis en œuvre: data F a = Either (Int -> a) (String -> a)
.
Un foncteur qui n'est pas d'application légale même si les méthodes de classe de type peuvent être implémentées:
data P a = P ((a -> Int) -> Maybe a)
Le constructeur de type P
est un foncteur car il n'utilise a
que dans des positions covariantes.
instance Functor P where
fmap :: (a -> b) -> P a -> P b
fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))
La seule implémentation possible de la signature de type de <*>
est une fonction qui renvoie toujours Nothing
:
(<*>) :: P (a -> b) -> P a -> P b
(P pfab) <*> (P pa) = \_ -> Nothing -- fails the laws!
Mais cette implémentation ne satisfait pas à la loi d'identité des foncteurs applicatifs.
- Un foncteur qui n'est
Applicative
pas unMonad
car la signature de type de bind
ne peut pas être implémentée.
Je ne connais pas de tels exemples!
- Un foncteur qui n'est
Applicative
pas unMonad
car les lois ne peuvent pas être satisfaites même si la signature de type de bind
peut être implémentée.
Cet exemple a généré pas mal de discussions, il est donc sûr de dire que prouver cet exemple n'est pas facile. Mais plusieurs personnes l'ont vérifié indépendamment par différentes méthodes. Voir Is `data PoE a = Empty | Paire aa` une monade? pour une discussion supplémentaire.
data B a = Maybe (a, a)
deriving Functor
instance Applicative B where
pure x = Just (x, x)
b1 <*> b2 = case (b1, b2) of
(Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
_ -> Nothing
Il est quelque peu lourd de prouver qu'il n'y a pas d' Monad
instance légale . La raison du comportement non monadique est qu'il n'y a aucun moyen naturel d'implémenter bind
quand une fonction f :: a -> B b
pourrait retourner Nothing
ou Just
pour différentes valeurs de a
.
Il est peut-être plus clair d'envisager Maybe (a, a, a)
, qui n'est pas non plus une monade, et d'essayer de l'implémenter join
pour cela. On constatera qu'il n'y a pas de moyen de mise en œuvre intuitivement raisonnable join
.
join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
join Nothing = Nothing
join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
-- etc.
Dans les cas indiqués par ???
, il semble clair que nous ne pouvons pas produire Just (z1, z2, z3)
de manière raisonnable et symétrique sur six valeurs de type différentes a
. Nous pourrions certainement choisir un sous-ensemble arbitraire de ces six valeurs, - par exemple, toujours prendre la première non vide Maybe
- mais cela ne satisferait pas les lois de la monade. Le retour Nothing
ne satisfera pas non plus aux lois.
- Une structure de données arborescente qui n'est pas une monade même si elle a une associativité
bind
- mais qui ne respecte pas les lois sur l'identité.
La monade arborescente habituelle (ou "un arbre avec des branches en forme de foncteur") est définie comme
data Tr f a = Leaf a | Branch (f (Tr f a))
Il s'agit d'une monade gratuite sur le foncteur f
. La forme des données est un arbre où chaque point de branchement est un "foncteur" de sous-arbres. L'arbre binaire standard serait obtenu avec type f a = (a, a)
.
Si nous modifions cette structure de données en faisant également les feuilles en forme de foncteur f
, nous obtenons ce que j'appelle une "semi-monade" - elle a bind
qui satisfait aux lois de naturalité et d'associativité, mais sa pure
méthode échoue à l'une des lois d'identité. "Les semi-monades sont des semi-groupes dans la catégorie des endofuncteurs, quel est le problème?" Ceci est la classe de type Bind
.
Par souci de simplicité, je définis la join
méthode au lieu de bind
:
data Trs f a = Leaf (f a) | Branch (f (Trs f a))
join :: Trs f (Trs f a) -> Trs f a
join (Leaf ftrs) = Branch ftrs
join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)
La greffe de branche est standard, mais la greffe de feuille est non standard et produit a Branch
. Ce n'est pas un problème pour la loi d'associativité mais viole l'une des lois d'identité.
Quand les types polynomiaux ont-ils des instances de monade?
Aucun des foncteurs Maybe (a, a)
et Maybe (a, a, a)
ne peut se voir attribuer une Monad
instance légale , bien qu'ils le soient évidemment Applicative
.
Ces foncteurs n'ont aucune astuce - pas Void
ou bottom
n'importe où, pas de paresse / rigueur délicate, pas de structures infinies et pas de contraintes de classe de type. L' Applicative
instance est complètement standard. Les fonctions return
et bind
peuvent être implémentées pour ces foncteurs mais ne satisferont pas aux lois de la monade. En d'autres termes, ces foncteurs ne sont pas des monades car il manque une structure spécifique (mais il n'est pas facile de comprendre ce qui manque exactement). Par exemple, un petit changement dans le foncteur peut en faire une monade: data Maybe a = Nothing | Just a
c'est une monade. Un autre foncteur similaire data P12 a = Either a (a, a)
est également une monade.
Constructions pour les monades polynomiales
En général, voici quelques constructions qui produisent des Monad
s licites à partir de types polynomiaux. Dans toutes ces constructions, M
est une monade:
type M a = Either c (w, a)
où w
est monoïde
type M a = m (Either c (w, a))
où m
est toute monade et w
tout monoïde
type M a = (m1 a, m2 a)
où m1
et où m2
sont les monades
type M a = Either a (m a)
où m
est toute monade
La première construction est WriterT w (Either c)
, la deuxième construction est WriterT w (EitherT c m)
. La troisième construction est un produit de monades pure @M
par composant : est définie comme le produit par composant de pure @m1
et pure @m2
, et join @M
est définie en omettant les données de produits croisés (par exemple, m1 (m1 a, m2 a)
est mappée m1 (m1 a)
en omettant la deuxième partie du tuple):
join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))
La quatrième construction est définie comme
data M m a = Either a (m a)
instance Monad m => Monad M m where
pure x = Left x
join :: Either (M m a) (m (M m a)) -> M m a
join (Left mma) = mma
join (Right me) = Right $ join @m $ fmap @m squash me where
squash :: M m a -> m a
squash (Left x) = pure @m x
squash (Right ma) = ma
J'ai vérifié que les quatre constructions produisent des monades légales.
Je suppose qu'il n'y a pas d'autres constructions pour les monades polynomiales. Par exemple, le foncteur Maybe (Either (a, a) (a, a, a, a))
n'est obtenu par aucune de ces constructions et n'est donc pas monadique. Cependant, Either (a, a) (a, a, a)
est monadique parce qu'il est isomorphe au produit de trois monades a
, a
, et Maybe a
. Aussi, Either (a,a) (a,a,a,a)
est monadique car il est isomorphe au produit de a
et Either a (a, a, a)
.
Les quatre constructions présentées ci-dessus nous permettront d'obtenir n'importe quelle somme de n'importe quel nombre de produits de n'importe quel nombre a
, par exemple Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
et ainsi de suite. Tous ces constructeurs de types auront (au moins une) Monad
instance.
Il reste à voir, bien sûr, quels cas d'utilisation pourraient exister pour de telles monades. Un autre problème est que les Monad
instances dérivées via les constructions 1-4 ne sont généralement pas uniques. Par exemple, le constructeur de type type F a = Either a (a, a)
peut recevoir une Monad
instance de deux manières: par la construction 4 en utilisant la monade (a, a)
, et par la construction 3 en utilisant l'isomorphisme de type Either a (a, a) = (a, Maybe a)
. Encore une fois, trouver des cas d'utilisation pour ces implémentations n'est pas immédiatement évident.
Une question demeure - étant donné un type de données polynomiales arbitraires, comment savoir s'il a une Monad
instance. Je ne sais pas prouver qu'il n'y a pas d'autres constructions pour les monades polynomiales. Je ne pense pas qu'il existe jusqu'à présent de théorie pour répondre à cette question.
* -> *
) pour lequel il n'existe pas de convenablefmap
?