tutorial - ¿Qué es "levantar" en Haskell?
learn you a haskell pdf (5)
Comencemos con un ejemplo:
> replicate 3 ''a''
"aaa"
> :t replicate
replicate :: Int -> a -> [a]
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f a -> f [a]
> (liftA2 replicate) [1,2,3] [''a'',''b'',''c'']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
liftA2
transforma una función de tipos simples en una función de estos tipos envueltos en un Applicative
, como listas, IO
, etc.
Otro ascensor común es lift
desde Control.Monad.Trans
. Transforma una acción monádica de una mónada a una acción de una mónada transformada.
En general, levanta "levantar" una función / acción en un tipo "envuelto".
La mejor manera de entender esto, mónadas, etc. y comprender por qué son útiles, es probablemente codificarlo y usarlo. Si hay algo que codificaste previamente que sospechas puede beneficiarse de esto (es decir, esto acortará el código, etc.), simplemente pruébalo y entenderás fácilmente el concepto.
No entiendo qué es "levantar". ¿Debería primero entender las mónadas antes de entender qué es un "ascensor"? (Igualmente, soy completamente ignorante sobre las mónadas :) ¿O alguien puede explicarme con palabras simples?
De acuerdo con este brillante tutorial , un funtor es un contenedor (como Maybe<a>
, List<a>
o Tree<a>
que puede almacenar elementos de algún otro tipo, a
). He utilizado la notación de genéricos de Java, <a>
, para el tipo de elemento a
y pienso en los elementos como bayas en el árbol Tree<a>
. Hay una función fmap
, que toma una función de conversión de elementos, a->b
y functor<a>
contenedor functor<a>
. Aplica a->b
a cada elemento del contenedor convirtiéndolo efectivamente en el functor<b>
. Cuando solo se proporciona el primer argumento, a->b
, fmap
espera al functor<a>
. Es decir, el suministro de a->b
convierte esta función de nivel de elemento en el functor<a> -> functor<b>
función functor<a> -> functor<b>
que opera sobre contenedores. Esto se llama elevación de la función. Debido a que el contenedor también se denomina functor , los Funtores en lugar de las Mónadas son un requisito previo para el levantamiento. Las mónadas son una especie de "paralelo" al levantamiento. Ambos confían en la noción de Functor y hacen f<a> -> f<b>
. La diferencia es que el levantamiento usa a->b
para la conversión mientras que Monad requiere que el usuario defina a -> f<b>
.
La elevación es más un patrón de diseño que un concepto matemático (aunque espero que alguien por aquí ahora me refute al mostrar cómo los ascensores son una categoría o algo así).
Normalmente tiene algún tipo de datos con un parámetro. Algo como
data Foo a = Foo { ...stuff here ...}
Supongamos que encuentra que muchos usos de Foo
toman tipos numéricos ( Int
, Double
, etc.) y sigue teniendo que escribir códigos que desenvuelven estos números, los agregan o los multiplican, y luego los envían nuevamente. Puede cortocircuitar esto escribiendo el código desenvolver y envolver una vez. Esta función se denomina tradicionalmente "elevación" porque se ve así:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
En otras palabras, tiene una función que toma una función de dos argumentos (como el operador (+)
) y la convierte en la función equivalente para Foos.
Entonces ahora puedes escribir
addFoo = liftFoo2 (+)
Editar: más información
Por supuesto, puede tener liftFoo3
, liftFoo4
y así sucesivamente. Sin embargo, esto a menudo no es necesario.
Comience con la observación
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Pero eso es exactamente lo mismo que fmap
. Entonces, en lugar de liftFoo1
escribirías
instance Functor Foo where
fmap foo = ...
Si realmente quieres una regularidad completa, puedes decir
liftFoo1 = fmap
Si puedes convertir a Foo
en un funtor, quizás puedas convertirlo en un funtor aplicativo. De hecho, si puede escribir liftFoo2
, la instancia aplicativa se liftFoo2
así:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap ''x'' inside a Foo.
(<*>) = liftFoo2 ($)
El operador (<*>)
para Foo tiene el tipo
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Aplica la función envuelta al valor envuelto. Entonces, si puede implementar liftFoo2
, puede escribir esto en términos de ello. O puede implementarlo directamente y no molestarse con liftFoo2
, porque el módulo Control.Applicative
incluye
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
y del mismo modo hay liftA
y liftA3
. Pero en realidad no los usa muy a menudo porque hay otro operador
(<$>) = fmap
Esto te permite escribir:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
El término myFunction <$> arg1
devuelve una nueva función envuelta en Foo. Esto a su vez se puede aplicar al siguiente argumento usando (<*>)
, y así sucesivamente. Así que ahora, en lugar de tener una función de elevación para cada aria, solo tienes una cadena de aplicativos.
La elevación es un concepto que le permite transformar una función en una función correspondiente dentro de otra configuración (generalmente más general)
echa un vistazo a http://haskell.org/haskellwiki/Lifting
Los de Paul y Yairchu son buenas explicaciones.
Me gustaría agregar que la función que se está levantando puede tener una cantidad arbitraria de argumentos y que no tienen que ser del mismo tipo. Por ejemplo, también puedes definir un ascensorFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
En general, el levantamiento de funciones que toman 1 argumento se captura en la clase de tipo Functor
, y la operación de elevación se denomina fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Tenga en cuenta la similitud con el tipo de liftFoo1
. De hecho, si tienes liftFoo1
, puedes hacer de Foo
una instancia de Functor
:
instance Functor Foo where
fmap = liftFoo1
Además, la generalización de la elevación a un número arbitrario de argumentos se denomina estilo aplicativo . No se moleste en sumergirse en esto hasta que capte el levantamiento de las funciones con un número fijo de argumentos. Pero cuando lo haga, Learn you a Haskell tiene un buen capítulo sobre esto. La Typeclassopedia es otro buen documento que describe Functor y Applicative (así como otras clases de tipos, desplácese hacia abajo al capítulo de la derecha en ese documento).
¡Espero que esto ayude!