Haskell Language
Anwendungsfähiger Functor
Suche…
Einführung
Applicative
ist die Klasse der Typen f :: * -> *
die eine übergeordnete Funktionsanwendung über eine Struktur ermöglicht, in der die Funktion ebenfalls in diese Struktur eingebettet ist.
Bemerkungen
Definition
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Beachten Sie die Functor
Einschränkung für f
. Die pure
Funktion gibt ihr in die Applicative
Struktur eingebettetes Argument zurück. Die Infix-Funktion <*>
(ausgesprochen "zutreffend") ist fmap
sehr ähnlich, abgesehen von der in die Applicative
Struktur eingebetteten Funktion.
Eine korrekte Instanz von Applicative
sollte die anwendbaren Gesetze erfüllen, diese werden jedoch vom Compiler nicht durchgesetzt:
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
Alternative Definition
Da jeder Applicative Functor ein Functor ist , kann fmap
immer verwendet werden. Daher ist das Wesentliche von Applicative die Kombination der mitgeführten Inhalte sowie die Fähigkeit, diese zu erstellen:
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
Diese Klasse ist isomorph zu Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
Umgekehrt,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Häufige Fälle von Applicativ
Könnte sein
Maybe
ein applikativer Funktor einen möglicherweise fehlenden Wert.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
hebt den angegebenen Wert in Maybe
an, indem Sie Just
auf ihn anwenden. Die Funktion (<*>)
wendet eine Funktion, die in einem Maybe
auf einen Wert in einem Maybe
. Wenn sowohl die Funktion als auch der Wert vorhanden sind (mit Just
), wird die Funktion auf den Wert angewendet und das umschlossene Ergebnis wird zurückgegeben. Wenn einer fehlt, kann die Berechnung nicht fortgesetzt werden und stattdessen wird Nothing
zurückgegeben.
Listen
Eine Möglichkeit, Listen an die Typunterschrift <*> :: [a -> b] -> [a] -> [b]
besteht darin, das kartesische Produkt der beiden Listen zu verwenden, wobei jedes Element der ersten Liste mit jedem Paar verbunden wird Element des zweiten:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Dies wird normalerweise als Emulation des Nichtdeterminismus interpretiert, wobei eine Liste von Werten für einen nichtdeterministischen Wert steht, dessen mögliche Werte über dieser Liste liegen; Eine Kombination zweier nichtdeterministischer Werte erstreckt sich also auf alle möglichen Kombinationen der Werte in den beiden Listen:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Unendliche Streams und Zip-Listen
Es gibt eine Klasse von Applicative
s, die ihre beiden Eingaben zusammenfassen. Ein einfaches Beispiel sind unendliche Ströme:
data Stream a = Stream { headS :: a, tailS :: Stream a }
Die Applicative
Instanz von Stream
wendet einen Stream von Funktionen punktweise auf einen Stream von Argumenten an, wobei die Werte in den beiden Streams nach Position zusammengefasst werden. pure
gibt einen konstanten Stream zurück - eine unendliche Liste eines einzelnen festen Werts:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Listen geben auch eine "flinke" Applicative
Instanz an, für die es den ZipList
ZipList- ZipList
gibt:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Da zip
sein Ergebnis nach der kürzesten Eingabe trimmt, ist die einzige Implementierung von pure
, die die Applicative
Gesetze erfüllt, eine, die eine unendliche Liste zurückgibt:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Zum Beispiel:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
Die beiden Möglichkeiten erinnern uns an das äußere und das innere Produkt, ähnlich dem Multiplizieren einer 1-Spalten-Matrix ( nx 1
) mit einer 1-Zeilen-Matrix ( 1 xm
) im ersten Fall, wobei die nxm
Matrix als Ergebnis erhalten wird (jedoch abgeflacht wird ); oder Multiplizieren einer 1-Zeilen- und einer 1-Spalten-Matrix (jedoch ohne das Summieren) im zweiten Fall.
Funktionen
Bei der Spezialisierung auf Funktionen (->) r
stimmen die Typunterschriften von pure
und <*>
mit denen der Kombinatoren K
und S
überein:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
muss const
, und <*>
nimmt ein Paar von Funktionen und wendet diese jeweils auf ein festes Argument an, wobei die beiden Ergebnisse angewendet werden:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Funktionen sind der prototypische "Zippy" -Applikativ. Da zum Beispiel unendliche Ströme isomorph zu (->) 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))
Durch die Darstellung von Streams in einer höheren Reihenfolge wird automatisch die fließende Applicative
Instanz erzeugt.