web-dev-qa-db-fra.com

Qu'est-ce que "lever" à Haskell?

Je ne comprends pas ce que "soulever" est. Dois-je d'abord comprendre les monades avant de comprendre ce qu'est un "ascenseur"? (Je suis aussi complètement ignorant des monades :) Ou quelqu'un peut-il m'expliquer avec des mots simples?

121
GabiMe

Le levage est plus un modèle de conception qu'un concept mathématique (même si je m'attends à ce que quelqu'un ici me réfute maintenant en montrant comment les ascenseurs sont une catégorie ou quelque chose du genre).

Vous avez généralement un type de données avec un paramètre. Quelque chose comme

data Foo a = Foo { ...stuff here ...}

Supposons que de nombreuses utilisations de Foo prennent des types numériques (Int, Double etc.) et que vous continuiez à devoir écrire du code qui décompresse ces nombres, les ajoute ou les multiplie, puis les remballe. Vous pouvez court-circuiter ceci en écrivant une fois le code de déballage et d’emballage. 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 (telle que l'opérateur (+)) et la transforme en une fonction équivalente pour Foos.

Alors maintenant, vous pouvez écrire

addFoo = liftFoo2 (+)

Edit: plus d'informations

Vous pouvez bien sûr avoir liftFoo3, liftFoo4 et ainsi de suite. Cependant, ce 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. Donc plutôt que liftFoo1 vous écririez

instance Functor Foo where
   fmap foo = ...

Si vous voulez vraiment une régularité complète, vous pouvez alors dire

liftFoo1 = fmap

Si vous pouvez transformer Foo en foncteur, vous pouvez peut-être en faire un foncteur applicatif. En fait, si vous pouvez écrire liftFoo2, l'instance d'application ressemble à ceci:

import Control.Applicative

instance Applicative Foo where
   pure x = Foo $ ...   -- Wrap 'x' inside a Foo.
   (<*>) = liftFoo2 ($)

L'opérateur (<*>) pour Foo a le type

(<*>) :: Foo (a -> b) -> Foo a -> Foo b

Il applique la fonction encapsulée à la valeur encapsulée. Donc, si vous pouvez implémenter liftFoo2, vous pouvez écrire ceci en termes de cela. Ou vous pouvez l’implémenter directement et ne pas déranger avec liftFoo2, car le module Control.Applicative inclut

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

de même, il existe liftA et liftA3. Mais vous ne les utilisez pas très souvent car il y a un autre opérateur

(<$>) = fmap

Cela vous permet d'écrire:

result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4

Le terme myFunction <$> arg1 renvoie une nouvelle fonction encapsulée dans Foo. Cela peut à son tour être appliqué au prochain argument en utilisant (<*>), etc. Alors maintenant, au lieu d’avoir une fonction d’ascenseur pour chaque arité, vous n’avez plus qu’une chaîne d’applications en guirlande.

160
Paul Johnson

Paul et yairchu sont deux bonnes explications.

J'aimerais ajouter que la fonction levée peut avoir un nombre arbitraire d'arguments et qu'il n'est pas nécessaire qu'ils soient du même type. Par exemple, vous pouvez également définir un liftFoo1:

liftFoo1 :: (a -> b) -> Foo a -> Foo b

En général, la levée des fonctions qui prennent un argument est capturée dans la classe de type Functor et l'opération de levée est appelée fmap:

fmap :: Functor f => (a -> b) -> f a -> f b

Notez la similitude avec le type de liftFoo1. En fait, si vous avez liftFoo1, vous pouvez faire de Foo une instance de Functor:

instance Functor Foo where
  fmap = liftFoo1

De plus, la généralisation de la levée à un nombre arbitraire d'arguments s'appelle style applicatif. Ne vous embêtez pas jusqu'à ce que vous compreniez la levée des fonctions avec un nombre fixe d'arguments. Mais quand vous le faites, Apprenez-vous un Haskell a un bon chapitre à ce sujet. Le Typeclassopedia est un autre bon document qui décrit Functor et Applicative (ainsi que d'autres classes de types; faites défiler vers le bas le chapitre de ce document).

J'espère que cela t'aides!

36
Martijn

Commençons par un exemple:

> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> a -> [a]
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f a -> f [a]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)

liftA2 transforme une fonction de types en clair en une fonction de ces types encapsulée dans une Applicative, telle que des listes, IO, etc.

Un autre ascenseur commun est lift from Control.Monad.Trans. Il transforme l'action monadique d'une monade en action d'une monade transformée.

En général, lève "lève" une fonction/action dans un type "enveloppé".

Le meilleur moyen de comprendre cela et les monades, etc. et de comprendre leur utilité, est probablement de le coder et de l'utiliser. Si vous croyez que quelque chose que vous avez codé précédemment pourrait en tirer parti (c’est-à-dire que le code sera plus court, etc.), essayez-le et vous comprendrez facilement le concept.

22
yairchu

Le levage est un concept qui vous permet de transformer une fonction en une fonction correspondante dans un autre paramètre (généralement plus général)

jetez un oeil à http://haskell.org/haskellwiki/Lifting

11
Nasser Hadjloo

Selon ce tutoriel brillant , un foncteur est un conteneur (comme Maybe<a>, List<a> ou Tree<a> pouvant stocker des éléments d’un autre type, a). J'ai utilisé la notation générique Java, <a>, pour le type d'élément a et considère les éléments comme des baies sur l'arbre Tree<a>. Il existe une fonction fmap, qui prend une fonction de conversion d'élément, a->b et un conteneur functor<a>. Il applique a->b à chaque élément du conteneur en le convertissant efficacement en functor<b>. Lorsque seul le premier argument est fourni, a->b, fmap attend le functor<a>. Autrement dit, fournir uniquement a->b transforme cette fonction au niveau élément en la fonction functor<a> -> functor<b> qui opère sur des conteneurs. Ceci s'appelle soulèvement de la fonction. Étant donné que le conteneur est également appelé a functor, les foncteurs plutôt que les monades sont une condition préalable au levage. Les monades sont en quelque sorte "parallèles" à la levée. Les deux s'appuient sur la notion de foncteur et font f<a> -> f<b>. La différence est que lifting utilise a->b pour la conversion, alors que Monad demande à l'utilisateur de définir a -> f<b>.

0
Val