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 .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow