Me gustaría proponer un enfoque más sistemático para responder esta pregunta, y también mostrar ejemplos que no usen ningún truco especial como los valores "inferiores" o los tipos de datos infinitos o algo así.
¿Cuándo los constructores de tipos no tienen instancias de clase de tipo?
En general, hay dos razones por las que un constructor de tipos podría no tener una instancia de una clase de tipo determinada:
- No se pueden implementar las firmas de tipo de los métodos necesarios de la clase de tipo.
- Puede implementar las firmas de tipo pero no puede cumplir con las leyes requeridas.
Los ejemplos del primer tipo son más fáciles que los del segundo tipo porque para el primer tipo, solo necesitamos verificar si uno puede implementar una función con una firma de tipo dada, mientras que para el segundo tipo, debemos demostrar que no hay implementación posiblemente podría satisfacer las leyes.
Ejemplos específicos
Este es un contrafunctor, no un functor, con respecto al parámetro tipo a
, porque está a
en una posición contravariante. Es imposible implementar una función con firma de tipo (a -> b) -> F z a -> F z b
.
Un constructor de tipos que no es un functor legal aunque fmap
se pueda implementar la firma de tipo de :
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!
El aspecto curioso de este ejemplo es que podemos poner en práctica fmap
del tipo correcto a pesar de que F
no es posible que sea un funtor, ya que utiliza a
en una posición contravariante. Por lo tanto, esta implementación que se fmap
muestra arriba es engañosa, a pesar de que tiene la firma de tipo correcta (creo que esta es la única implementación posible de esa firma de tipo), las leyes de functor no están satisfechas. Por ejemplo, fmap id
≠ id
, porque let (Q(f,_)) = fmap id (Q(read,"123")) in f "456"
es 123
, pero let (Q(f,_)) = id (Q(read,"123")) in f "456"
es 456
.
De hecho, F
es solo un profunctor, no es ni un functor ni un contrafunctor.
Un functor legal que no es aplicativo porque la firma de tipo de pure
no se puede implementar: tome la mónada Writer (a, w)
y elimine la restricción que w
debería ser un monoide. Entonces es imposible construir un valor de tipo a (a, w)
partir de a
.
Un funtor aplicativo que no es porque el tipo de firma <*>
no pueden ser implementadas: data F a = Either (Int -> a) (String -> a)
.
Un functor que no es legalmente aplicativo a pesar de que se pueden implementar los métodos de clase de tipo:
data P a = P ((a -> Int) -> Maybe a)
El constructor de tipos P
es un functor porque a
solo se usa en posiciones covariantes.
instance Functor P where
fmap :: (a -> b) -> P a -> P b
fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))
La única implementación posible de la firma de tipo de <*>
es una función que siempre devuelve Nothing
:
(<*>) :: P (a -> b) -> P a -> P b
(P pfab) <*> (P pa) = \_ -> Nothing -- fails the laws!
Pero esta implementación no satisface la ley de identidad para los actores aplicativos.
- Un functor que es
Applicative
pero no unMonad
porque la firma de tipo de bind
no se puede implementar.
¡No conozco ninguno de esos ejemplos!
- Un functor que es
Applicative
pero noMonad
porque las leyes no se pueden cumplir a pesar de que bind
se puede implementar la firma de tipo .
Este ejemplo ha generado bastante discusión, por lo que es seguro decir que demostrar que este ejemplo es correcto no es fácil. Pero varias personas han verificado esto independientemente por diferentes métodos. Ver Es `datos PoE a = Vacío | Par aa` una mónada? para discusión adicional.
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
Es algo engorroso demostrar que no hay una Monad
instancia legal . La razón del comportamiento no monádico es que no hay una forma natural de implementar bind
cuándo una función f :: a -> B b
podría regresar Nothing
o Just
para diferentes valores de a
.
Quizás sea más claro considerarlo Maybe (a, a, a)
, que tampoco es una mónada, y tratar de implementarlo join
. Uno encontrará que no hay una forma intuitiva razonable de implementación 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.
En los casos indicados por ???
, parece claro que no podemos producir Just (z1, z2, z3)
de manera razonable y simétrica a partir de seis valores diferentes de tipo a
. Ciertamente podríamos elegir algún subconjunto arbitrario de estos seis valores, por ejemplo, siempre tomar el primer no vacío Maybe
, pero esto no satisfaría las leyes de la mónada. Regresar Nothing
tampoco satisfará las leyes.
- Una estructura de datos en forma de árbol que no es una mónada a pesar de que tiene asociatividad
bind
, pero no cumple con las leyes de identidad.
La mónada usual en forma de árbol (o "un árbol con ramas en forma de functor") se define como
data Tr f a = Leaf a | Branch (f (Tr f a))
Esta es una mónada gratis sobre el functor f
. La forma de los datos es un árbol donde cada punto de ramificación es un "functor-ful" de subárboles. El árbol binario estándar se obtendría con type f a = (a, a)
.
Si modificamos esta estructura de datos haciendo también las hojas en forma de functor f
, obtenemos lo que yo llamo una "semimonada", bind
que cumple con las leyes de naturalidad y asociatividad, pero su pure
método falla una de las leyes de identidad. "Los semimonads son semigrupos en la categoría de endofunctores, ¿cuál es el problema?" Esta es la clase de tipo Bind
.
Por simplicidad, defino el join
método en lugar 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)
El injerto de rama es estándar, pero el injerto de hoja no es estándar y produce a Branch
. Esto no es un problema para la ley de asociatividad, pero rompe una de las leyes de identidad.
¿Cuándo los tipos polinómicos tienen instancias de mónada?
Ninguno de los funtores Maybe (a, a)
y Maybe (a, a, a)
se puede dar un legítimo Monad
ejemplo, a pesar de que son obviamente Applicative
.
Estos functores no tienen trucos, Void
ni en bottom
ningún lado, ni pereza / rigidez engañosas, ni estructuras infinitas, ni restricciones de clase de tipo. La Applicative
instancia es completamente estándar. Las funciones return
y bind
se pueden implementar para estos functores pero no satisfarán las leyes de la mónada. En otras palabras, estos functores no son mónadas porque falta una estructura específica (pero no es fácil entender qué es exactamente lo que falta). Como ejemplo, un pequeño cambio en el functor puede convertirlo en una mónada: data Maybe a = Nothing | Just a
es una mónada. Otro functor similar data P12 a = Either a (a, a)
es también una mónada.
Construcciones para mónadas polinómicas
En general, aquí hay algunas construcciones que producen Monad
s legales a partir de tipos polinomiales. En todas estas construcciones, M
es una mónada:
type M a = Either c (w, a)
donde w
hay un monoide
type M a = m (Either c (w, a))
donde m
hay alguna mónada y w
es cualquier monoide
type M a = (m1 a, m2 a)
donde m1
y m2
son mónadas
type M a = Either a (m a)
¿Dónde m
está alguna mónada?
La primera construcción es WriterT w (Either c)
, la segunda construcción es WriterT w (EitherT c m)
. La tercera construcción es un producto de mónadas pure @M
por componentes : se define como el producto por componentes de pure @m1
y pure @m2
, y join @M
se define omitiendo datos de productos cruzados (por ejemplo, m1 (m1 a, m2 a)
se asigna m1 (m1 a)
omitiendo la segunda parte de la tupla):
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 cuarta construcción se define como
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
He comprobado que las cuatro construcciones producen mónadas legales.
Yo conjeturo que no hay otras construcciones para mónadas polinómicas. Por ejemplo, el functor Maybe (Either (a, a) (a, a, a, a))
no se obtiene a través de ninguna de estas construcciones y, por lo tanto, no es monádico. Sin embargo, Either (a, a) (a, a, a)
es monádica porque es isomorfo al producto de tres mónadas a
, a
, y Maybe a
. Además, Either (a,a) (a,a,a,a)
es monádico porque es isomorfo al producto de a
y Either a (a, a, a)
.
Las cuatro construcciones mostradas arriba nos permitirán obtener cualquier suma de cualquier número de productos de cualquier número de a
's, por ejemplo, Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a))
etc. Todos los constructores de tipo tendrán (al menos una) Monad
instancia.
Queda por ver, por supuesto, qué casos de uso podrían existir para tales mónadas. Otro problema es que las Monad
instancias derivadas a través de las construcciones 1-4 en general no son únicas. Por ejemplo, al constructor de tipos type F a = Either a (a, a)
se le puede dar una Monad
instancia de dos maneras: mediante la construcción 4 con la mónada (a, a)
y mediante la construcción 3 con el isomorfismo de tipo Either a (a, a) = (a, Maybe a)
. Nuevamente, encontrar casos de uso para estas implementaciones no es inmediatamente obvio.
Queda una pregunta: dado un tipo de datos polinómico arbitrario, cómo reconocer si tiene una Monad
instancia. No sé cómo demostrar que no hay otras construcciones para mónadas polinómicas. No creo que exista ninguna teoría hasta ahora para responder a esta pregunta.
* -> *
) para el cual no existe un adecuadofmap
?