haskell - Cómo usar(->) instancias de Monad y confusión sobre(->)
functional-programming monads (2)
En diferentes preguntas, he encontrado sugerencias en los comentarios sobre el uso de la (->)
instancia de Mónadas, por ejemplo, para realizar un estilo sin puntos.
En cuanto a mí, esto es un poco demasiado abstracto. Bien, he visto instancias de Arrow en (->)
y me parece que (->)
se puede usar en notaciones de instancias pero no en declaraciones de tipos (eso solo sería lo mismo para otra pregunta).
¿Alguien tiene ejemplos de usar (->)
como instancia de Monad? O un buen enlace?
Disculpe si esta pregunta ya se debatió aquí, pero buscar " (->)
instancia de Monad" le da muchos hits como se puede imaginar ... ya que casi todas las preguntas sobre Haskell implican (->)
o "Monad" .
Para definir una mónada para (->) r
, necesitamos dos operaciones, return
y (>>=)
, sujetas a tres leyes:
instance Monad ((->) r) where
Si miramos la firma de retorno para (->) r
return :: a -> r -> a
podemos ver que es solo la función constante, que ignora su segundo argumento.
return a r = a
O alternativamente,
return = const
Para compilar (>>=)
, si especializamos su firma de tipo con la mónada (->) r
,
(>>=) :: (r -> a) -> (a -> r -> b) -> r -> b
en realidad solo hay una definición posible.
(>>=) x y z = y (x z) z
Usar esta mónada es como transmitir un argumento adicional r
a cada función. Puede usar esto para configuración, o para pasar opciones muy abajo en las entrañas de su programa.
Podemos verificar que sea una mónada, verificando las tres leyes de la mónada:
1. return a >>= f = f a
return a >>= f
= (/b -> a) >>= f -- by definition of return
= (/x y z -> y (x z) z) (/b -> a) f -- by definition of (>>=)
= (/y z -> y ((/b -> a) z) z) f -- beta reduction
= (/z -> f ((/b -> a) z) z) -- beta reduction
= (/z -> f a z) -- beta reduction
= f a -- eta reduction
2. m >>= return = m
m >>= return
= (/x y z -> y (x z) z) m return -- definition of (>>=)
= (/y z -> y (m z) z) return -- beta reduction
= (/z -> return (m z) z) -- beta reduction
= (/z -> const (m z) z) -- definition of return
= (/z -> m z) -- definition of const
= m -- eta reduction
La ley de la mónada final:
3. (m >>= f) >>= g ≡ m >>= (/x -> f x >>= g)
sigue por un razonamiento ecuacional similar y fácil.
Podemos definir un número de otras clases para ((->) r) también, como Functor,
instance Functor ((->) r) where
y si miramos la firma de
-- fmap :: (a -> b) -> (r -> a) -> r -> b
¡podemos ver que es solo una composición!
fmap = (.)
Del mismo modo, podemos hacer una instancia de Applicative
instance Applicative ((->) r) where
-- pure :: a -> r -> a
pure = const
-- (<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
(<*>) g f r = g r (f r)
Lo bueno de tener estas instancias es que te permiten emplear todos los combinadores de Monad y Applicative al manipular funciones.
Hay muchas instancias de clases que implican (->), por ejemplo, puede escribir a mano la instancia de Monoid para (b -> a), dado un Monoid en a
como:
enter code here
instance Monoid a => Monoid (b -> a) where
-- mempty :: Monoid a => b -> a
mempty _ = mempty
-- mappend :: Monoid a => (b -> a) -> (b -> a) -> b -> a
mappend f g b = f b `mappend` g b
pero dada la instancia de Monad / Applicative, también puede definir esta instancia con
instance Monoid a => Monoid (r -> a) where
mempty = pure mempty
mappend = liftA2 mappend
utilizando la instancia Aplicable para (->) r
o con
instance Monoid a => Monoid (r -> a) where
mempty = return mempty
mappend = liftM2 mappend
usando la instancia de Monad para (->) r
.
Aquí los ahorros son mínimos, pero, por ejemplo, la herramienta @pl para generar código sin puntos, que es proporcionada por lambdabot en el canal IRC #haskell, abusa de estas instancias bastante.
Para un tipo dado r
, la función de tipo r -> a
puede considerarse como un cálculo que entrega un a
usando un entorno tipeado r
. Dadas dos funciones r -> a
y a -> (r -> b)
, es fácil imaginar que uno puede componer estos cuando se le da un entorno (una vez más, de tipo r
).
¡Pero espera! ¡Eso es exactamente de lo que se trata la mónada!
Entonces podemos crear una instancia de Mónada para (->) r
que implemente f >>= g
pasando el r
a ambos f
y g
. Esto es lo que hace la instancia de Monad para (->) r
.
Para acceder realmente al entorno, puede usar id :: r -> r
, que ahora puede pensar como un cálculo que se ejecuta en un entorno r
y que entrega una r
. Para crear subentornos locales, puede usar lo siguiente:
inLocalEnvironment :: (r -> r) -> (r -> a) -> (r -> a)
inLocalEnvironment xform f = /env -> f (xform env)
Este patrón de tener un entorno pasado a computaciones que luego puede consultarlo y modificarlo localmente es útil no solo para la mónada (->) r
, que es la razón por la cual se abstrae en la clase MonadReader
, usando nombres mucho más sensibles que los que yo lo he usado aquí:
http://hackage.haskell.org/packages/archive/mtl/2.0.1.0/doc/html/Control-Monad-Reader-Class.html
Básicamente, tiene dos instancias: (->) r
que hemos visto aquí, y ReaderT rm
, que es simplemente un envoltorio newtype
alrededor de r -> ma
, entonces es lo mismo que la (->) r
mónada I '' Aquí se describe, excepto que proporciona cálculos en alguna otra mónada transformada.