Haskell Language
Functor Aplicativo
Buscar..
Introducción
Applicative
es la clase de tipos f :: * -> *
que permite la aplicación de funciones elevadas sobre una estructura donde la función también está incrustada en esa estructura.
Observaciones
Definición
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Tenga en cuenta la restricción de Functor
en f
. La función pure
devuelve su argumento incrustado en la estructura Applicative
. La función de infijo <*>
(pronunciada "aplicar") es muy similar a fmap
excepto con la función incorporada en la estructura Applicative
.
Una instancia correcta de Applicative
debería satisfacer las leyes aplicativas , aunque estas no son aplicadas por el compilador:
pure id <*> a = a -- identity
pure (.) <*> a <*> b <*> c = a <*> (b <*> c) -- composition
pure f <*> pure a = pure (f a) -- homomorphism
a <*> pure b = pure ($ b) <*> a -- interchange
Definición alternativa
Dado que cada fmap
Functor es un Functor , fmap
siempre se puede usar en él; Por lo tanto, la esencia de Applicative es el emparejamiento de contenidos transportados, así como la capacidad de crearlo:
class Functor f => PairingFunctor f where
funit :: f () -- create a context, carrying nothing of import
fpair :: (f a,f b) -> f (a,b) -- collapse a pair of contexts into a pair-carrying context
Esta clase es isomorfa al Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
A la inversa,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Ejemplos comunes de aplicativo
Tal vez
Maybe
sea un funtor aplicativo que contenga un valor posiblemente ausente.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
levanta el valor dado en Maybe
aplicando Just
a él. La función (<*>)
aplica una función envuelta en un Maybe
a un valor en Maybe
. Si tanto la función como el valor están presentes (construidos con Just
), la función se aplica al valor y se devuelve el resultado final. Si falta alguno, el cálculo no puede continuar y, en su lugar, Nothing
se devuelve Nothing
.
Liza
Una forma para que las listas se ajusten a la firma de tipo <*> :: [a -> b] -> [a] -> [b]
es tomar el producto cartesiano de las dos listas, emparejando cada elemento de la primera lista con cada uno Elemento del segundo:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Esto generalmente se interpreta como emulando el no determinismo, con una lista de valores representando un valor no determinista cuyos valores posibles se extienden sobre esa lista; por lo tanto, una combinación de dos valores no deterministas abarca todas las combinaciones posibles de los valores en las dos listas:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Flujos infinitos y listas zip
Hay una clase de Applicative
que "comprimen" sus dos entradas juntas. Un ejemplo simple es el de las corrientes infinitas:
data Stream a = Stream { headS :: a, tailS :: Stream a }
La instancia Applicative
Stream
aplica un flujo de funciones a un flujo de argumentos puntuales, emparejando los valores en los dos flujos por posición. pure
devuelve una secuencia constante, una lista infinita de un solo valor fijo:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Las listas también admiten una instancia Applicative
"zippy", para la que existe el ZipList
ZipList:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Dado que zip
recorta su resultado de acuerdo con la información más corta, la única implementación de pure
que cumple con las leyes de Applicative
es una que devuelve una lista infinita:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Por ejemplo:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
Las dos posibilidades nos recuerdan el producto externo y el interno, similar a multiplicar una matriz de 1 columna ( nx 1
) con una de 1 fila ( 1 xm
) en el primer caso, obteniendo la matriz nxm
como resultado (pero aplanada) ); o multiplicar las matrices de 1 fila y 1 columna (pero sin resumir) en el segundo caso.
Funciones
Cuando se especializa en funciones (->) r
, las firmas de tipo pure
y <*>
coinciden con las de los combinadores K
y S
, respectivamente:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
debe ser const
, y <*>
toma un par de funciones y las aplica a un argumento fijo, aplicando los dos resultados:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Las funciones son el prototípico "zippy" aplicativo. Por ejemplo, dado que las corrientes infinitas son isomorfas para (->) Nat
, ...
-- | Index into a stream
to :: Stream a -> (Nat -> a)
to (Stream x xs) Zero = x
to (Stream x xs) (Suc n) = to xs n
-- | List all the return values of the function in order
from :: (Nat -> a) -> Stream a
from f = from' Zero
where from' n = Stream (f n) (from' (Suc n))
... la representación de flujos en una orden superior produce automáticamente la instancia de Zippy Applicative
.