Las clases de tipos de Haskell de la biblioteca estándar MonadPlus
, Alternative
y Monoid
cada una proporciona dos métodos con esencialmente la misma semántica:
- Un valor vacío:
mzero
,empty
omempty
. - Un operador
a -> a -> a
que une los valores de la clase de tipos juntos:mplus
,<|>
omappend
.
Los tres especifican estas leyes a las que deben adherirse las instancias:
mempty `mappend` x = x
x `mappend` mempty = x
Por lo tanto, parece que las tres clases de tipos proporcionan los mismos métodos.
( Alternative
también proporciona some
y many
, pero sus definiciones predeterminadas suelen ser suficientes, por lo que no son demasiado importantes en términos de esta pregunta).
Entonces, mi consulta es: ¿por qué estas tres clases extremadamente similares? ¿Existe alguna diferencia real entre ellos, además de sus diferentes restricciones de superclase?
Applicative
yMonadPlus
parecen ser exactamente iguales (restricciones de superclase de módulo).ArrowZero
yArrowPlus
para flechas. Mi apuesta: hacer que las firmas de tipos sean más limpias (lo que hace que las diferentes restricciones de superclase sean la diferencia real).ArrowZero
yArrowPlus
tener tipo* -> * -> *
, lo que significa que puede pasarlos por el tipo de flecha una vez para una función que necesita usarlos para una multitud de tipos, para usar unMonoid
tendría que requerir una instancia deMonoid
para cada particular instanciación, y no tendría garantía de que se manejaran de manera similar, ¡las instancias podrían no estar relacionadas!Respuestas:
MonadPlus
yMonoid
sirven para diferentes propósitos.A
Monoid
está parametrizado sobre un tipo de género*
.class Monoid m where mempty :: m mappend :: m -> m -> m
y así se puede instanciar para casi cualquier tipo para el que existe un operador obvio que es asociativo y que tiene una unidad.
Sin embargo,
MonadPlus
no solo especifica que tienes una estructura monoidal, sino también que esa estructura está relacionada con cómoMonad
funciona, y que a esa estructura no le importa el valor contenido en la mónada, esto está (en parte) indicado por el hecho esoMonadPlus
requiere un argumento de tipo* -> *
.class Monad m => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a
Además de las leyes de monoides, tenemos dos conjuntos de leyes potenciales a las que podemos aplicar
MonadPlus
. Lamentablemente, la comunidad no está de acuerdo con lo que deberían ser.Al menos sabemos
mzero >>= k = mzero
pero hay otras dos extensiones en competencia, la ley de distribución izquierda (sic)
mplus a b >>= k = mplus (a >>= k) (b >>= k)
y la ley de captura de la izquierda
mplus (return a) b = return a
Por tanto, cualquier instancia de
MonadPlus
debería satisfacer una o ambas de estas leyes adicionales.¿Y qué pasa
Alternative
?Applicative
se definió despuésMonad
, y lógicamente pertenece como una superclase deMonad
, pero en gran parte debido a las diferentes presiones sobre los diseñadores en Haskell 98, ni siquieraFunctor
fue una superclase deMonad
hasta 2015. Ahora finalmente tenemosApplicative
como superclase deMonad
en GHC (si no aún en un idioma estándar.)Efectivamente,
Alternative
es aApplicative
lo queMonadPlus
esMonad
.Por estos obtendríamos
empty <*> m = empty
de manera análoga a lo que tenemos con
MonadPlus
y existen propiedades distributivas y de captura similares, al menos una de las cuales debe satisfacer.Desafortunadamente, incluso la
empty <*> m = empty
ley es una afirmación demasiado fuerte. ¡No es válido para Backwards , por ejemplo!Cuando miramos a MonadPlus, la ley de vacío >> = f = vacío casi se nos impone. La construcción vacía no puede tener ninguna 'a' para llamar a la función
f
de todos modos.Sin embargo, dado que
Applicative
es no una superclase deMonad
yAlternative
es no una superclase deMonadPlus
, terminamos definiendo ambos casos por separado.Además, incluso si
Applicative
fuera una superclase deMonad
, terminarías necesitando laMonadPlus
clase de todos modos, porque incluso si obedeciéramosempty <*> m = empty
eso no es estrictamente suficiente para demostrar que
empty >>= f = empty
Entonces, afirmar que algo es un
MonadPlus
es más fuerte que afirmar que lo esAlternative
.Ahora, por convención,
MonadPlus
yAlternative
para un tipo dado deberían coincidir, peroMonoid
pueden ser completamente diferentes.Por ejemplo, el
MonadPlus
yAlternative
paraMaybe
hacer lo obvio:instance MonadPlus Maybe where mzero = Nothing mplus (Just a) _ = Just a mplus _ mb = mb
pero la
Monoid
instancia eleva un semigrupo a unMonoid
. Lamentablemente, debido a que no existía unaSemigroup
clase en ese momento en Haskell 98, lo hace solicitando aMonoid
, pero sin usar su unidad. ಠ_ಠinstance Monoid a => Monoid (Maybe a) where mempty = Nothing mappend (Just a) (Just b) = Just (mappend a b) mappend Nothing x = x mappend x Nothing = x mappend Nothing Nothing = Nothing
TL; DR
MonadPlus
es un reclamo más fuerte queAlternative
, que a su vez es un reclamo más fuerte queMonoid
, y aunque las instanciasMonadPlus
yAlternative
para un tipo deben estar relacionadas,Monoid
puede ser (y a veces es) algo completamente diferente.fuente
mempty `mappend` x ≡ x
.MonadPlus
eAlternative
implementaciones?Monad
que es unAlternative
pero no unMonadPlus
. Hice una pregunta sobre cómo encontrar un ejemplo específico de esto; si conoces alguno, me encantaría verlo.MonadPlus
lo que en realidad hay dos clases disfrazadas como una porque a la mayoría de las personas no les importa.