Haskell Language
Applicative Functor
Szukaj…
Wprowadzenie
Applicative
to klasa typów f :: * -> *
która umożliwia zastosowanie funkcji zniesionej nad strukturą, w której funkcja jest również osadzona w tej strukturze.
Uwagi
Definicja
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Zwróć uwagę na ograniczenie Functor
na f
. Funkcja pure
zwraca swój argument osadzony w strukturze Applicative
. Funkcja infix <*>
(wymawiane „ fmap
”) jest bardzo podobna do fmap
z wyjątkiem funkcji osadzonej w strukturze Applicative
.
Prawidłowa instancja Applicative
powinna być zgodna z obowiązującymi przepisami , choć nie są one egzekwowane przez kompilator:
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
Alternatywna definicja
Ponieważ każdy aplikacyjnych funktora jest funktora , fmap
zawsze mogą być stosowane na nim; dlatego istotą Aplikacji jest parowanie przewożonych treści, a także możliwość ich tworzenia:
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
Ta klasa jest izomorficzna dla Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
Odwrotnie,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Typowe przypadki zastosowania
Może
Maybe
to funktor aplikacyjny zawierający ewentualnie nieobecną wartość.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
podnosi określoną wartość do Maybe
poprzez zastosowanie Just
do niej. Funkcja (<*>)
stosuje funkcję zawiniętą w Maybe
do wartości w Maybe
. Jeśli zarówno funkcja, jak i wartość są obecne (zbudowane za pomocą Just
), funkcja jest stosowana do wartości, a zawijany wynik jest zwracany. Jeśli brakuje któregoś z nich, obliczenia nie mogą być kontynuowane i zamiast tego Nothing
jest zwracane.
Listy
Jednym ze sposobów dopasowania list do podpisu typu <*> :: [a -> b] -> [a] -> [b]
jest wzięcie produktu kartezjańskiego z dwóch list, łącząc każdy element pierwszej listy z każdym element drugiego:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Jest to zwykle interpretowane jako emulacja niedeterminizmu, z listą wartości oznaczających niedeterministyczną wartość, której możliwe wartości mieszczą się w obrębie tej listy; więc kombinacja dwóch niedeterministycznych zakresów wartości dla wszystkich możliwych kombinacji wartości na dwóch listach:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Nieskończone strumienie i listy zip
Istnieje klasa Applicative
które „zipują” swoje dwa wejścia razem. Jednym prostym przykładem jest nieskończony strumień:
data Stream a = Stream { headS :: a, tailS :: Stream a }
Stream
jest Applicative
instancji stosuje się strumień funkcji do strumienia argumenty wskazują-mądry, łącząc się wartości w dwóch strumieni o położeniu. pure
zwraca stały strumień - nieskończoną listę pojedynczej stałej wartości:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Listy również dopuszczają instancję „zippy” Applicative
, dla której istnieje nowy ZipList
ZipList:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Ponieważ zip
przycina swój wynik według najkrótszego wejścia, jedynego realizacji pure
spełniającym Applicative
praw jest jedna, która zwraca listę nieskończone:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Na przykład:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
Dwie możliwości przypominają nam iloczyn zewnętrzny i wewnętrzny, podobny do pomnożenia macierzy 1-kolumnowej ( nx 1
) i macierzy 1-rzędowej ( 1 xm
) w pierwszym przypadku, uzyskując w nxm
macierz nxm
(ale spłaszczoną ); lub pomnożenie macierzy 1-rzędowej i 1-kolumnowej (ale bez sumowania) w drugim przypadku.
Funkcje
Gdy specjalizujemy się w funkcjach (->) r
, sygnatury typów pure
i <*>
zgodne odpowiednio z kombinatorami K
i S
:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
musi być const
, a <*>
bierze parę funkcji i stosuje je każdą do stałego argumentu, stosując dwa wyniki:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Funkcje to prototypowa aplikacja „zippy”. Na przykład, ponieważ strumienie nieskończone są izomorficzne dla (->) 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))
... reprezentowanie strumieni w sposób wyższego rzędu automatycznie tworzy zippyną instancję Applicative
.