Te recomendamos que revises esta respuesta en un ambiente controlado antes de enviarlo a producción, saludos.
Solución:
Una mónada se puede definir en términos de:
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
o alternativamente en términos de:
return :: a -> m a
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
A tus preguntas:
- No, no podemos definir
fmap
en términos dejoin
, ya que de lo contrario podríamos eliminarfmap
de la segunda lista anterior. - No, “cada mónada es un funtor” es una declaración sobre las mónadas en general, independientemente de si define su mónada específica en términos de
bind
o en términos dejoin
yfmap
. Es más fácil entender la declaración si ve la segunda definición, pero eso es todo. - Sí,
bind
es más “poderoso” quejoin
. Es exactamente tan “poderoso” comojoin
yfmap
combinado, si quiere decir con “poderoso” que tiene la capacidad de definir una mónada (siempre en combinación conreturn
).
Para una intuición, vea, por ejemplo, esta respuesta: bind
le permite combinar o encadenar estrategias / planes / cálculos (que están en un contexto) juntos. Como ejemplo, usemos el Maybe
contexto (o Maybe
monada):
λ: let plusOne x = Just (x + 1)
λ: Just 3 >>= plusOne
Just 4
fmap
también le permite encadenar los cálculos en un contexto juntos, pero a costa de aumentar el anidamiento con cada paso.[1]
λ: fmap plusOne (Just 3)
Just (Just 4)
Es por eso que necesitas join
: aplastar dos niveles de anidación en uno. Recordar:
join :: m (m a) -> m a
Tener solo el paso de aplastamiento no te lleva muy lejos. También necesitas fmap
tener una mónada – y return
, cual es Just
en el ejemplo anterior.
[1]: fmap
y (>>=)
no tome sus dos argumentos en el mismo orden, pero no deje que eso lo confunda.
Hay un [definition] de
fmap
en términos dejoin
?
No, no lo hay. Eso se puede demostrar al intentar hacerlo. Supongamos que se nos da un constructor de tipo arbitrario T
, y funciones:
returnT :: a -> T a
joinT :: T (T a) -> T a
Solo a partir de estos datos, queremos definir:
fmapT :: (a -> b) -> T a -> T b
Así que dibujémoslo:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = tb
where
tb = undefined -- tb :: T b
Necesitamos obtener un valor de tipo T b
de alguna manera. ta :: T a
por sí solo no funcionará, por lo que necesitamos funciones que produzcan T b
valores. Los únicos dos candidatos son joinT
y returnT
. joinT
no ayuda:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = joinT ttb
where
ttb = undefined -- ttb :: T (T b)
Simplemente patea la lata por el camino, como si necesitara un T (T b)
El valor en estas circunstancias no mejora.
Podríamos intentar returnT
en lugar de:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT b
where
b = undefined -- b :: b
Ahora necesitamos un b
valor. Lo único que puede darnos uno es f
:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f a)
where
a = undefined -- a :: a
Y ahora estamos estancados: nada puede darnos un a
. Hemos agotado todas las posibilidades disponibles, por lo que fmapT
no se puede definir en tales términos.
Una digresión: no sería suficiente hacer trampa usando una función como esta:
extractT :: T a -> a
Con un extractT
, podríamos intentar a = extractT ta
, llevando a:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f (extractT ta))
Sin embargo, no es suficiente para fmapT
para tener el tipo correcto: también debe seguir las leyes de functor. En particular, fmapT id = id
debe aguantar. Con esta definición, fmapT id
es returnT . extractT
, que, en general, no es id
(la mayoría de los functores que son instancias de ambos Monad
y Comonad
sirven como ejemplos).
¿Es “cada mónada es un funtor” una conclusión cuando se usa
bind
(en la definición de una mónada), pero una suposición al usarjoin
?
“Toda mónada es un funtor” es una suposición o, más precisamente, parte de la definición de mónada. Para elegir una ilustración arbitraria, aquí está Emily Riehl, Teoría de categorías en contexto, pag. 154:
Definición 5.1.1. A monada en una categoría C consta de
un endofunctor T : C → C,
a unidad transformación natural η : 1C ⇒ T, y
a multiplicación transformación natural μ :T2 ⇒ T,
para que los siguientes diagramas conmuten en CC: [diagrams of the monad laws]
Por tanto, una mónada implica un endofunctor por definición. Para un constructor de tipo Haskell T
que instancia Monad
, el mapeo de objetos de ese endofunctor es T
sí mismo, y el mapeo del morfismo es su fmap
. Ese T
será un Functor
instancia, y por lo tanto tendrá una fmap
, está, en Haskell contemporáneo, garantizado por Applicative
(y, por extensión, Functor
) siendo una superclase de Monad
.
Sin embargo, ¿es esa toda la historia? En lo que respecta a Haskell. lo sabemos liftM
existe, y también que en un pasado no muy lejano Functor
no era una superclase de Monad
. ¿Son esos dos hechos meros haskellismos? No exactamente. En el papel clásico Nociones de computación y mónadasEugenio Moggi desentierra la siguiente definición (p. 3):
Definición 1.2 ([Man76]) A Kleisli triple sobre una categoría C es un triple (T, η, _ *), dónde T : Obj (C) → Obj (C), ηA : A → TA por A ∈ Obj (C), f *: TA → TB por f: A → TB y se cumplen las siguientes ecuaciones:
- ηA* = idejército de reserva
- ηA; f * = f por f: A → TB
- F*; g * = (f; g *) * por f: A → TB y g: B → TC
El detalle importante aquí es que T se presenta como un mero mapeo de objetos en la categoría C, y no como endofunctor en C. Trabajando en el Hask categoría, que equivale a tomar un constructor de tipos T
sin presuponer que es un Functor
ejemplo. En código, podríamos escribir eso como:
class KleisliTriple t where
return :: a -> t a
(=<<) :: (a -> t b) -> t a -> t b
-- (return =<<) = id
-- (f =<<) . return = f
-- (g =<<) . (f =<<) = ((g =<<) . f =<<)
Dejando de lado el enlace, esa es la definición de pre-AMP de Monad
en Haskell. Como era de esperar, el artículo de Moggi no tarda mucho en mostrar que "existe una correspondencia uno a uno entre las triples y las mónadas de Kleisli" (p. 5), estableciendo a lo largo del camino que T puede extenderse a un endofunctor (en Haskell, ese paso equivale a definir el mapeo de morfismo liftM f m = return . f =<< m
, y luego mostrarlo sigue las leyes de functor).
Con todo, si escribe definiciones legales de return
y (>>=)
sin presuponer fmap
, de hecho obtiene una implementación legal de Functor
Como consecuencia. "Existe una correspondencia biunívoca entre las tripletas y las mónadas de Kleisli" es una consecuencia de la definición de triple de Kleisli, mientras que "una mónada implica un endofunctor" es parte de la definición de mónada. Es tentador considerar si sería más exacto describir lo que hizo Haskellers al escribir Monad
casos como "establecer un triple de Klesili" en lugar de "establecer una mónada", pero me abstendré por temor a quedar atrapado en la pedantería terminológica, y en cualquier caso, ahora que Functor
es una superclase de Monad
no hay ninguna razón práctica para preocuparse por eso.
Es
bind
más "poderoso" quejoin
? ¿Y qué significaría "más poderoso"?
¡Pregunta capciosa!
Tomada al pie de la letra, la respuesta sería sí, en la medida en que, junto con return
, (>>=)
hace posible implementar fmap
(vía liftM
, como se señaló anteriormente), mientras que join
no lo hace. Sin embargo, no creo que valga la pena insistir en esta distinción. ¿Porque? Por las leyes de las mónadas. Al igual que no tiene sentido hablar de un legal(>>=)
sin presuponer return
, no tiene sentido hablar de un join
sin presionar return
yfmap
.
Uno podría tener la impresión de que estoy dando demasiado peso a las leyes al usarlas para atar Monad
y Functor
De este modo. Está true que hay casos de leyes que involucran a dos clases, y que solo se aplican a tipos que las instancian a ambas. Foldable
proporciona un buen ejemplo de eso: podemos encontrar la siguiente ley en el Traversable
documentación:
Las instancias de superclase deben satisfacer lo siguiente: [...]
En el
Foldable
ejemplo,foldMap
debe ser equivalente a transversal con un functor aplicativo constante (foldMapDefault
).
Que esta ley específica no siempre se aplique no es un problema, porque no la necesitamos para caracterizar lo que Foldable
es (las alternativas incluyen "a Foldable
es un contenedor del que podemos extraer una secuencia de elementos ", y" un Foldable
es un contenedor que se puede convertir al monoide libre en su tipo de elemento "). Sin embargo, con las leyes de la mónada, no es así: el significado de la clase está indisolublemente ligado a las tres leyes de la mónada.
Valoraciones y reseñas
Si sostienes algún recelo o capacidad de medrar nuestro enunciado eres capaz de añadir una acotación y con placer lo leeremos.