La levée est plus un modèle de conception qu'un concept mathématique (bien que je m'attende à ce que quelqu'un ici me réfute en montrant que les levées sont une catégorie ou autre).
Typiquement, vous avez un certain type de données avec un paramètre. Quelque chose comme
data Foo a = Foo { ...stuff here ...}
Supposons que vous trouvez que beaucoup d'utilisations de Foo
prennent des types numériques ( Int
, Double
etc.) et vous devez sans cesse écrire du code qui déballe ces nombres, les ajoute ou les multiplie, puis les emballe à nouveau. Vous pouvez court-circuiter ce processus en n'écrivant qu'une seule fois le code de déballage et d'enroulement. Cette fonction est traditionnellement appelée "ascenseur" car elle ressemble à ceci :
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
En d'autres termes, vous avez une fonction qui prend une fonction à deux arguments (comme la fonction (+)
) et le transforme en fonction équivalente pour Foos.
Donc maintenant vous pouvez écrire
addFoo = liftFoo2 (+)
Edit : plus d'informations
Vous pouvez bien sûr avoir liftFoo3
, liftFoo4
et ainsi de suite. Cependant, cela n'est souvent pas nécessaire.
Commencez par l'observation
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Mais c'est exactement la même chose que fmap
. Ainsi, plutôt que de liftFoo1
vous écrivez
instance Functor Foo where
fmap f foo = ...
Si vous voulez vraiment une régularité totale, vous pouvez alors dire
liftFoo1 = fmap
Si vous pouvez faire Foo
en un foncteur, peut-être pouvez-vous en faire un foncteur applicatif. En fait, si vous pouvez écrire liftFoo2
alors l'instance applicative ressemble à ceci :
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
El (<*>)
L'opérateur pour Foo a le type
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Il applique la fonction enveloppée à la valeur enveloppée. Donc, si vous pouvez implémenter liftFoo2
alors vous pouvez écrire ceci en fonction de cela. Ou bien vous pouvez l'implémenter directement et ne pas vous embarrasser de liftFoo2
parce que le Control.Applicative
Le module comprend
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
et de même, il y a liftA
y liftA3
. Mais vous ne les utilisez pas très souvent car il existe un autre opérateur.
(<$>) = fmap
Cela vous permet d'écrire :
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
Le terme myFunction <$> arg1
renvoie une nouvelle fonction enveloppée dans Foo :
ghci> :type myFunction
a -> b -> c -> d
ghci> :type myFunction <$> Foo 3
Foo (b -> c -> d)
Ceci peut à son tour être appliqué à l'argument suivant en utilisant (<*>)
et ainsi de suite. Donc maintenant, au lieu d'avoir une fonction de levée pour chaque arité, vous avez juste une chaîne d'applicatifs, comme ceci :
ghci> :type myFunction <$> Foo 3 <*> Foo 4
Foo (c -> d)
ghci: :type myFunction <$> Foo 3 <*> Foo 4 <*> Foo 5
Foo d
10 votes
Peut-être utile, peut-être pas : haskell.org/haskellwiki/Lifting