Haskell Language
Foncteur applicatif
Recherche…
Introduction
Applicative
est la classe de types f :: * -> *
qui permet une application de fonction soulevée sur une structure où la fonction est également incorporée dans cette structure.
Remarques
Définition
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Notez la contrainte Functor
sur f
. La fonction pure
renvoie son argument incorporé dans la structure Applicative
. La fonction infixe <*>
(prononcée "apply") est très similaire à fmap
sauf avec la fonction intégrée dans la structure Applicative
.
Une instance correcte de Applicative
devrait satisfaire aux lois applicatives , bien que celles-ci ne soient pas appliquées par le compilateur:
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
Définition alternative
Comme chaque foncteur applicatif est un foncteur , fmap
peut toujours y être utilisé; Ainsi, l'essence de Applicative est l'appariement des contenus transportés, ainsi que la possibilité de le créer:
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
Cette classe est isomorphe à Applicative
.
pure a = const a <$> funit = a <$ funit
fa <*> fb = (\(a,b) -> a b) <$> fpair (fa, fb) = uncurry ($) <$> fpair (fa, fb)
Inversement,
funit = pure ()
fpair (fa, fb) = (,) <$> fa <*> fb
Instances courantes d'application
Peut être
Maybe
- Maybe
est-ce un foncteur applicatif contenant une valeur éventuellement absente.
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just $ f x
_ <*> _ = Nothing
pure
lève la valeur donnée dans Maybe
en lui appliquant Just
. La fonction (<*>)
applique une fonction enveloppée dans un Maybe
à une valeur dans Maybe
. Si la fonction et la valeur sont présentes (construites avec Just
), la fonction est appliquée à la valeur et le résultat encapsulé est renvoyé. Si l'un ou l'autre est manquant, le calcul ne peut pas continuer et Nothing
n'est renvoyé à la place.
Des listes
Une façon pour les listes de correspondre à la signature de type <*> :: [a -> b] -> [a] -> [b]
consiste à prendre le produit cartésien des deux listes, en associant chaque élément de la première liste à chaque élément du second:
fs <*> xs = [f x | f <- fs, x <- xs]
-- = do { f <- fs; x <- xs; return (f x) }
pure x = [x]
Ceci est généralement interprété comme une émulation du non-déterminisme, avec une liste de valeurs représentant une valeur non-déterministe dont les valeurs possibles sont comprises dans cette liste; ainsi, une combinaison de deux valeurs non déterministes se situe sur toutes les combinaisons possibles des valeurs des deux listes:
ghci> [(+1),(+2)] <*> [3,30,300]
[4,31,301,5,32,302]
Flux infinis et listes zip
Il y a une classe de Applicative
qui "compressent" leurs deux entrées ensemble. Un exemple simple est celui des flux infinis:
data Stream a = Stream { headS :: a, tailS :: Stream a }
Applicative
instance Applicative
Stream
applique un flux de fonctions à un flux d'arguments au niveau des points, en associant les valeurs des deux flux par position. pure
renvoie un flux constant - une liste infinie d'une seule valeur fixe:
instance Applicative Stream where
pure x = let s = Stream x s in s
Stream f fs <*> Stream x xs = Stream (f x) (fs <*> xs)
Les listes admettent également une instance Applicative
"zippy", pour laquelle il existe le newtype ZipList
:
newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
ZipList xs <*> ZipList ys = ZipList $ zipWith ($) xs ys
Puisque zip
ajuste son résultat en fonction de l'entrée la plus courte, la seule implémentation de pure
satisfaisant aux lois Applicative
est celle qui renvoie une liste infinie:
pure a = ZipList (repeat a) -- ZipList (fix (a:)) = ZipList [a,a,a,a,...
Par exemple:
ghci> getZipList $ ZipList [(+1),(+2)] <*> ZipList [3,30,300]
[4,32]
Les deux possibilités nous rappellent le produit externe et interne, similaire à la multiplication d’une matrice à 1 colonne ( nx 1
) avec une matrice à 1 rangée ( 1 xm
) dans le premier cas, obtenant la matrice nxm
(mais aplatie ); ou en multipliant les matrices à une ligne et à une colonne (mais sans les additionner) dans le second cas.
Les fonctions
Lorsqu'elles sont spécialisées dans les fonctions (->) r
, les signatures de type pure
et <*>
correspondent respectivement à celles des combinateurs K
et S
:
pure :: a -> (r -> a)
<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
pure
doit être const
, et <*>
prend une paire de fonctions et les applique à un argument fixe, en appliquant les deux résultats suivants:
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> f x (g x)
Les fonctions sont le prototypique "zippy" applicative. Par exemple, puisque les flux infinis sont isomorphes à (->) 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 représentation des flux de manière plus ordonnée produit automatiquement l'instance Applicative
zippy.