Haskell Language
Toepasselijke Functor
Zoeken…
Invoering
Applicative
is de klasse van typen f :: * -> *
waarmee een opgeheven functie kan worden toegepast op een structuur waarbij de functie ook in die structuur is ingebed.
Opmerkingen
Definitie
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Let op de Functor
op f
. De pure
functie retourneert zijn argument ingebed in de Applicative
structuur. De infix-functie <*>
(uitgesproken als "toepassen") lijkt erg op fmap
behalve met de functie ingebed in de Applicative
structuur.
Een correct exemplaar van Applicative
moet voldoen aan de applicatieve wetgeving , hoewel deze niet worden afgedwongen door de compiler:
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
Alternatieve definitie
Aangezien elke Applicative Functor een Functor is , kan er altijd fmap
op worden gebruikt; de essentie van Applicative is dus het koppelen van gedragen inhoud, evenals de mogelijkheid om het te maken:
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
Deze klasse is isomorf voor Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
Omgekeerd,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Veel voorkomende gevallen van toepassing
Kan zijn
Maybe
is een applicatief functor die een mogelijk-afwezige waarde bevat.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
tilt de gegeven waarde in Maybe
door Just
te passen. De functie (<*>)
past een functie gewikkeld in een Maybe
op een waarde in een Maybe
. Als zowel de functie als de waarde aanwezig zijn (geconstrueerd met Just
), wordt de functie op de waarde toegepast en wordt het samengevoegde resultaat geretourneerd. Als een van beide ontbreekt, kan de berekening niet doorgaan en wordt er Nothing
geretourneerd.
lijsten
Een manier voor lijsten om te passen bij de typeaanduiding <*> :: [a -> b] -> [a] -> [b]
is om het Cartesiaanse product van de twee lijsten te nemen, waarbij elk element van de eerste lijst aan elk wordt gekoppeld element van de tweede:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Dit wordt meestal geïnterpreteerd als het emuleren van niet-determinisme, met een lijst met waarden die staat voor een niet-deterministische waarde waarvan de mogelijke waarden variëren over die lijst; dus een combinatie van twee niet-deterministische waarden varieert over alle mogelijke combinaties van de waarden in de twee lijsten:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Oneindige streams en zip-lijsten
Er is een klasse van Applicative
's die hun twee ingangen samen "zipen". Een eenvoudig voorbeeld is dat van oneindige streams:
data Stream a = Stream { headS :: a, tailS :: Stream a }
De Applicative
instantie van Stream
past een stroom van functies puntsgewijs toe op een stroom argumenten, waarbij de waarden in de twee stromen per positie worden gekoppeld. pure
retourneert een constante stroom - een oneindige lijst met één vaste waarde:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Lijsten laten ook een "zippy" Applicative
instantie toe, waarvoor het ZipList
type ZipList
bestaat:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Aangezien zip
zijn resultaat bijsnijdt volgens de kortste invoer, is de enige implementatie van pure
die voldoet aan de Applicative
wetgeving er een die een oneindige lijst retourneert:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Bijvoorbeeld:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
De twee mogelijkheden herinneren ons aan het buitenste en het binnenste product, vergelijkbaar met het vermenigvuldigen van een matrix met 1 kolom ( nx 1
) met een matrix met 1 rij ( 1 xm
) in het eerste geval, waardoor de nxm
matrix wordt verkregen (maar afgeplat ); of vermenigvuldigen van een 1-rij en een 1-kolom matrices (maar zonder de samenvatting) in het tweede geval.
functies
Wanneer gespecialiseerd in functies (->) r
, komen de typeaanduidingen van pure
en <*>
overeen met die van respectievelijk de K
en S
combinators:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
moet const
zijn en <*>
neemt een paar functies en past ze elk toe op een vast argument, waarbij de twee resultaten worden toegepast:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Functies zijn de prototypische "zippy" applicatie. Omdat oneindige stromen bijvoorbeeld isomorf zijn voor (->) 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))
... door streams op een hogere volgorde weer te geven, wordt de zippy Applicative
instantie automatisch geproduceerd.