haskell - learn - monad example
¿Cómo se compone una ap frombe? (4)
Disculpas por la respuesta lacónica y mecánica. No me gustan las cosas que me gusta elegir, como Applicative o Monad, pero no sé dónde estás. Este no es mi enfoque habitual para enseñar Haskell .
Primero, ap
está realmente (<*>)
debajo del capó.
Prelude> import Control.Monad
Prelude> import Data.Maybe
Prelude> import Control.Applicative
Prelude> :t ap
ap :: Monad m => m (a -> b) -> m a -> m b
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
¿Qué significa esto? Significa que no necesitamos algo tan "fuerte" como Monad para describir lo que estamos haciendo. Suficiente aplicativo. Sin embargo, Functor no.
Prelude> :info Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
Aquí está ap
/ (<*>)
con el Tal vez Monad / Aplicativo:
Prelude> ap (Just (+1)) (Just 1)
Just 2
Prelude> (<*>) (Just (+1)) (Just 1)
Just 2
Lo primero que debemos averiguar es, ¿de qué instancia de la clase de Typus estamos hablando?
Prelude> :t fromMaybe
fromMaybe :: a -> Maybe a -> a
Desugarse del tipo de Maybe un poco nos da:
(->) a (Maybe a -> a)
Así que el constructor de tipos que nos ocupa aquí es (->)
. ¿Qué nos dice GHCi acerca de (->)
también conocidos como tipos de funciones?
Prelude> :info (->)
data (->) a b -- Defined in ‘GHC.Prim’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
Hrm ¿Qué tal tal vez?
Prelude> :info Maybe
data Maybe a = Nothing | Just a -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
Lo que sucedió con el uso de (<*>)
para Maybe fue esto:
Prelude> (+1) 1
2
Prelude> (+1) `fmap` Just 1
Just 2
Prelude> Just (+1) <*> Just 1
Just 2
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> let mFmap = fmap :: (a -> b) -> Maybe a -> Maybe b
Prelude> (+1) `mFmap` Just 1
Just 2
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude> let mAp = (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Prelude> :t (+1)
(+1) :: Num a => a -> a
Prelude> :t Just (+1)
Just (+1) :: Num a => Maybe (a -> a)
Prelude> Just (+1) `mAp` Just 1
Just 2
Bien, ¿qué pasa con el Functor y el Aplicativo del tipo de función? Una de las partes difíciles aquí es que (->)
debe aplicarse parcialmente en el tipo para ser un Functor / Applicative / Monad. Entonces, f
convierte en (->) a
del (->) ab
global donde a
es un tipo de argumento y b
es el resultado.
Prelude> (fmap (+1) (+2)) 0
3
Prelude> (fmap (+1) (+2)) 0
3
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> let funcMap = fmap :: (a -> b) -> (c -> a) -> c -> b
Prelude> -- f ~ (->) c
Prelude> (funcMap (+1) (+2)) 0
3
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude> let funcAp = (<*>) :: (c -> a -> b) -> (c -> a) -> (c -> b)
Prelude> :t fromMaybe
fromMaybe :: a -> Maybe a -> a
Prelude> :t funcAp fromMaybe
funcAp fromMaybe :: (b -> Maybe b) -> b -> b
Prelude> :t const
const :: a -> b -> a
Prelude> :t funcAp const
funcAp const :: (b -> b1) -> b -> b
No se garantiza ser útil. Se puede decir que funcAp const
no es interesante solo por el tipo y saber cómo funciona la parametricidad.
Edición: hablando de componer, el Functor para (->) a
es solo (.)
. Aplicativo es eso, pero con un argumento extra. La mónada es el aplicativo, pero con los argumentos invertidos.
Whuttery adicional: el aplicativo <*>
para (->) a
) es S y pure
es K del cálculo del combinador SKI. (Puede derivar I de K y S. En realidad, puede derivar cualquier programa de K y S.)
Prelude> :t pure
pure :: Applicative f => a -> f a
Prelude> :t const
const :: a -> b -> a
Prelude> :t const
const :: a -> b -> a
Prelude> let k = pure :: a -> b -> a
Prelude> k 1 2
1
Prelude> const 1 2
1
Ahí estaba yo, escribiendo una función que toma un valor como entrada, llama a una función en esa entrada, y si el resultado es Just x
, debería devolver x
; de lo contrario, debería devolver la entrada original.
En otras palabras, esta función (que no sabía cómo llamar):
foo :: (a -> Maybe a) -> a -> a
foo f x = fromMaybe x (f x)
Ya que parece ser una función de propósito general, me pregunté si no estaba ya definida, así que pregunté en Twitter , y Chris Allen respondió que es una ap fromMaybe
.
Eso sonaba prometedor, así que encendí GHCI y comencé a experimentar:
Prelude Control.Monad Data.Maybe> :type ap
ap :: Monad m => m (a -> b) -> m a -> m b
Prelude Control.Monad Data.Maybe> :type fromMaybe
fromMaybe :: a -> Maybe a -> a
Prelude Control.Monad Data.Maybe> :type ap fromMaybe
ap fromMaybe :: (b -> Maybe b) -> b -> b
El tipo de ap fromMaybe
parece ciertamente correcto, y un par de experimentos parecen indicar que también tiene el comportamiento deseado.
Pero, ¿cómo funciona?
La función fromMaybe
me parece clara y, de forma aislada, creo que entiendo lo que hace ap
, al menos en el contexto de Maybe
. Cuando m
es Maybe
, tiene el tipo Maybe (a -> b) -> Maybe a -> Maybe b
.
Lo que no entiendo es cómo una ap fromMaybe
incluso compilar. Para mí, esta expresión parece una aplicación parcial, pero es posible que me esté equivocando. Si este es el caso, sin embargo, no entiendo cómo los tipos coinciden.
El primer argumento para ap
es m (a -> b)
, pero de fromMaybe
tenga el tipo a -> Maybe a -> a
. ¿Cómo encaja eso? ¿En qué instancia de Monad
el compilador infiere que m
es? ¿Cómo fromMaybe
, que toma dos argumentos (al curry), se convierte en una función que toma un solo argumento?
¿Puede alguien ayudarme a conectar los puntos?
La mónada que estás buscando es (->) r
o r -> _
si prefieres la sintaxis de infijo.
Entonces la firma de ap
expande a:
m (a -> b) -> m a -> m b =
(r -> (a -> b)) -> (r -> a) -> r -> b = -- now we use the signature of fromMaybe
(b -> (Maybe b -> b)) -> (b -> Maybe b) -> b -> b
Ahora, si considera ap fromMaybe
como una función parcialmente aplicada y voila obtendrá el resultado deseado.
Pero ese uso de ap
no está en el contexto de Maybe
. Lo estamos utilizando con una función, desde fromMaybe
, por lo que está en el contexto de las funciones, donde
ap f g x = f x (g x)
Entre las diversas instancias de Monad
tenemos
instance Monad ((->) r)
así es
ap :: Monad m => m (a -> b) -> m a -> m b
fromMaybe :: r -> (Maybe r -> r)
ap :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
ap f g x :: b
ap fromMaybe :: (r -> a) -> (r -> b) , a ~ Maybe r , b ~ r
porque ->
en tipos se asocia a la derecha: a -> b -> c ~ a -> (b -> c)
. Tratando de conectar los tipos juntos, solo podemos terminar con la definición anterior.
Y con (<*>) :: Applicative f => f (a -> b) -> fa -> fb
, podemos escribirlo como (fromMaybe <*>)
, si te gusta este tipo de graffiti:
#> :t (fromMaybe <*>)
(fromMaybe <*>) :: (r -> Maybe r) -> r -> r
Como se señala con razón en otra respuesta aquí, cuando se usa con funciones, <*>
es simplemente su buen combinador ole '' S. No podemos tener una función llamada S
en Haskell, así que <*>
es solo una parte del repertorio estándar del estilo de codificación sin puntos. El enlace monádico (más aún, volteado), =<<
, puede ser incluso más misterioso, pero a un programador de punto libre no le importa y lo usará felizmente para codificar otro patrón similar,
(f =<< g) x = f (g x) x
en llamadas de función combinatory , misterio o no misterio ( zipWith (-) =<< drop 1
viene a la mente).
Voy a volver a etiquetar los argumentos de tipo, para mayor claridad.
ap :: Monad m => m (a -> b) -> m a -> m b
fromMaybe :: c -> Maybe c -> c
¿En qué instancia de Monad el compilador infiere que m es?
((->) r)
es una Monad
. Se trata de todas las funciones que tienen el tipo r
como argumento, para algunas r
específicas .
Así que en el tipo:
ap :: Monad m => m (a -> b) -> m a -> m b
m
~ (c ->)
, a
~ Maybe c
y b
~ c
.
El tipo de retorno, ma -> mb
, se expande a (c -> Maybe c) -> c -> c
- que es el tipo de ap fromMaybe
.