yourself you monad learn data haskell applicative

you - funciona como funcionadores aplicativos(Haskell/LYAH)



monads learn you a haskell (3)

El capítulo 11 de Aprende a ti Haskell presenta la siguiente definición:

instance Applicative ((->) r) where pure x = (/_ -> x) f <*> g = /x -> f x (g x)

Aquí, el autor realiza gestos de mano poco característicos ("La implementación de la instancia para <*> es un poco críptica, por lo que es mejor si simplemente [lo mostramos en acción sin explicarlo]"). Espero que alguien aquí me ayude a resolverlo.

De acuerdo con la definición de clase aplicativa, (<*>) :: f (a -> b) -> fa -> fb

En la instancia, sustituyendo ((->)r) por f : r->(a->b)->(r->a)->(r->b)

Entonces, la primera pregunta, ¿cómo puedo obtener ese tipo para f <*> g = /x -> fx (gx) ?

Pero incluso si tomo esa última fórmula por sentado, tengo problemas para que esté de acuerdo con los ejemplos que doy a GHCi. Por ejemplo:

Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4 17

En su lugar, esta expresión parece consistente con f <*> g = /x -> f (gx) (tenga en cuenta que en esta versión x no aparece después de f .

Me doy cuenta de que esto es complicado, así que gracias por tener paciencia conmigo.


"En la instancia, sustituyendo ((->)r) por f : r->(a->b)->(r->a)->(r->b) "

Por qué, eso no está bien. En realidad es (r->(a->b)) -> (r->a) -> (r->b) , y eso es lo mismo que (r->a->b) -> (r->a) -> r -> b . Es decir, mapeamos un infijo y una función que devuelve el infijo ''argumento de la mano derecha, a una función que toma solo el infijo'' LHS y devuelve su resultado. Por ejemplo,

Prelude Control.Applicative> (:) <*> (/x -> [x]) $ 2 [2,2]


Antes que nada, recuerda cómo se define fmap para los fmap :

fmap f x = pure f <*> x

Esto significa que su ejemplo es el mismo que (fmap (+ 5) (* 3)) 4 . La función fmap para funciones es solo composición, por lo que su expresión exacta es la misma que ((+ 5) . (* 3)) 4 .

Ahora, pensemos por qué la instancia está escrita tal como es. Lo que <*> hace es esencialmente aplicar una función en el funtor a un valor en el funtor. Especializado en (->) r , esto significa que aplica una función devuelta por una función de r a un valor devuelto por una función de r . Una función que devuelve una función es solo una función de dos argumentos. Entonces la verdadera pregunta es esta: ¿cómo aplicarías una función de dos argumentos ( r y a , devolviendo b ) a un valor a devuelto por una función de r ?

Lo primero que debe observar es que debe devolver un valor de tipo (->) r que significa que el resultado también debe ser una función de r . Como referencia, aquí está la función <*> :

f <*> g = /x -> f x (g x)

Como queremos devolver una función tomando un valor de tipo r , x :: r . La función que devolvemos debe tener un tipo r -> b . ¿Cómo podemos obtener un valor de tipo b ? Bueno, tenemos una función f :: r -> a -> b . Como r va a ser el argumento de la función de resultado, obtenemos eso de forma gratuita. Entonces ahora tenemos una función de a -> b . Entonces, mientras tengamos algún valor de tipo a , podemos obtener un valor de tipo b . Pero, ¿cómo obtenemos un valor de tipo a ? Bueno, tenemos otra función g :: r -> a . Entonces podemos tomar nuestro valor de tipo r (el parámetro x ) y usarlo para obtener un valor de tipo a .

Entonces, la idea final es simple: utilizamos el parámetro para obtener primero un valor de tipo a conectándolo a g . El parámetro tiene tipo r , g tiene tipo r -> a , entonces tenemos una a . Luego, conectamos el parámetro y el nuevo valor en f . Necesitamos ambos porque f tiene un tipo r -> a -> b . Una vez que conectamos tanto una r como a a, tenemos una b1 . Como el parámetro está en lambda, el resultado tiene un tipo r -> b , que es lo que queremos.


Repasando tu pregunta original, creo que hay un punto sutil pero muy importante que podrías haber pasado por alto. Usando el ejemplo original de LYAH:

(+) <$> (+3) <*> (*100) $ 5

Esto es lo mismo que:

pure (+) <*> (+3) <*> (*100) $ 5

La clave aquí es el pure antes (+) , que tiene el efecto de boxeo (+) como aplicativo. Si observa qué tan pure está definido, puede ver que para desempaquetarlo, debe proporcionar un argumento adicional, que puede ser cualquier cosa. Aplicando <*> a (+) <$> (+3) , obtenemos

/x -> (pure (+)) x ((+3) x)

Observe en (pure (+)) x , estamos aplicando x a pure a unbox (+) . Entonces ahora tenemos

/x -> (+) ((+3) x)

Al agregar (*100) para obtener (+) <$> (+3) <*> (*100) y aplicar <*> nuevo, obtenemos

/x -> (+) ((+3) x) ((*100) x)

Entonces, en conclusión, la x después de f NO es el primer argumento para nuestro operador binario, se usa para DESBOX el operador dentro de pure .