No entiendo qué es "levantar". ¿Debería entender primero las mónadas antes de entender qué es un "ascensor"? (También soy completamente ignorante acerca de las mónadas :) ¿O alguien puede explicármelo con palabras simples?
No entiendo qué es "levantar". ¿Debería entender primero las mónadas antes de entender qué es un "ascensor"? (También soy completamente ignorante acerca de las mónadas :) ¿O alguien puede explicármelo con palabras simples?
Respuestas:
Levantar es más un patrón de diseño que un concepto matemático (aunque espero que alguien por aquí ahora me refute al mostrarme cómo los elevadores son una categoría o algo así).
Por lo general, tiene algún tipo de datos con un parámetro. Algo como
data Foo a = Foo { ...stuff here ...}
Suponga que encuentra muchos usos de Foo
los tipos numéricos de toma ( Int
, Double
etc.) y sigue teniendo que escribir código que desenvuelva estos números, los sume o los multiplique y luego los envuelva de nuevo. Puede cortocircuitar esto escribiendo el código de desenvolver y envolver una vez. Esta función se llama 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 que puede liftFoo3
, liftFoo4
y así sucesivamente. Sin embargo, esto a menudo no es necesario.
Comienza 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
instance Functor Foo where
fmap f foo = ...
Si realmente quieres una regularidad completa, puedes decir
liftFoo1 = fmap
Si puedes convertirte Foo
en un functor, quizás puedas convertirlo en un functor aplicativo. De hecho, si puede escribir liftFoo2
, la instancia aplicativa se ve así:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
El (<*>)
operador de 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 liftFoo2
, porque el Control.Applicative
módulo incluye
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
e igualmente 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. Entonces, en lugar de tener una función de elevación para cada arity, solo tiene una cadena de candidatos.
lift id == id
y lift (f . g) == (lift f) . (lift g)
.
id
y .
son la identidad de la flecha y la composición de la flecha de alguna categoría, respectivamente. Por lo general, cuando se habla de Haskell, la categoría en cuestión es "Hask", cuyas flechas son funciones de Haskell (en otras palabras, id
y se .
refieren a las funciones de Haskell que conoce y ama).
instance Functor Foo
, no instance Foo Functor
, ¿verdad? Me editaría a mí mismo pero no estoy 100% seguro.
Paul's y yairchu's son buenas explicaciones.
Me gustaría agregar que la función que se levanta puede tener un número arbitrario de argumentos y que no tienen que ser del mismo tipo. Por ejemplo, también podría definir un liftFoo1:
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 levantamiento se llama fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Tenga en cuenta la similitud con liftFoo1
el tipo de. De hecho, si tiene liftFoo1
, puede hacer Foo
una instancia de Functor
:
instance Functor Foo where
fmap = liftFoo1
Además, la generalización de levantar a un número arbitrario de argumentos se llama estilo aplicativo . No se moleste en sumergirse en esto hasta que comprenda el levantamiento de funciones con un número fijo de argumentos. Pero cuando lo haces, Learn you a Haskell tiene un buen capítulo sobre esto. El Typeclassopedia es otro buen documento que describe Functor y Aplicativo (así como otras clases de tipos; de desplazamiento hacia abajo a la derecha capítulo en ese documento).
¡Espero que esto ayude!
Comencemos con un ejemplo (se agrega algo de espacio en blanco para una presentación más clara):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
transforma una función de tipos simples en una función de los mismos tipos envueltos en unApplicative
, como listas IO
, etc.
Otro ascensor común es lift
de Control.Monad.Trans
. Transforma una acción monádica de una mónada en una acción de una mónada transformada.
En general, "levantar" eleva una función / acción a un tipo "envuelto" (por lo que la función original funciona "en secreto").
La mejor manera de entender esto, y mónadas, etc., y comprender por qué son útiles, es probablemente codificarlo y usarlo. Si hay algo que codificó anteriormente que sospecha que puede beneficiarse de esto (es decir, esto hará que ese código sea más corto, etc.), simplemente pruébelo y comprenderá fácilmente el concepto.
Lifting 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
De acuerdo con este tutorial brillante , un functor es un contenedor (como Maybe<a>
, List<a>
o Tree<a>
que puede almacenar elementos de algún otro tipo a
). He usado la notación genérica 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 un contenedor functor<a>
. Se aplica a->b
a cada elemento del contenedor convirtiéndolo efectivamente en functor<b>
. Cuando solo se proporciona el primer argumento a->b
, fmap
espera el functor<a>
. Es decir, el suministro a->b
solo convierte esta función de nivel de elemento en la función functor<a> -> functor<b>
que opera sobre contenedores. Esto se llama levantarde la función. Debido a que el contenedor también se llama functor , los Functors en lugar de las Mónadas son un requisito previo para el levantamiento. Las mónadas son algo "paralelas" a la elevación. Ambos confían en la noción de Functor y lo hacen f<a> -> f<b>
. La diferencia es que el levantamiento utiliza a->b
para la conversión, mientras que Monad requiere que el usuario lo defina a -> f<b>
.
r
a un tipo (usemos c
para variedad), son Functores. No "contienen" ninguno c
. En este caso, fmap es la composición de funciones, tomando una a -> b
función y una r -> a
, para darle una nueva r -> b
función. Todavía no hay contenedores. Además, si pudiera, lo marcaría nuevamente para la oración final.
fmap
es una función y no "espera" nada; El "contenedor" siendo un Functor es el punto principal de elevación. Además, las mónadas son, en todo caso, una doble idea para levantar: una mónada le permite usar algo que se ha levantado un número positivo de veces, como si solo se hubiera levantado una vez; esto se conoce mejor como aplanamiento .
To wait
, to expect
, to anticipate
son los sinónimos. Al decir "la función espera" quise decir "la función anticipa".
b = 5 : a
y f 0 = 55
f n = g n
, ambos implican seudo mutar el "contenedor". También el hecho de que las listas generalmente se almacenan completamente en la memoria, mientras que las funciones se almacenan típicamente como un cálculo. Pero las listas memorables / monorfas que no se almacenan entre llamadas rompen esa idea.