Je ne comprends pas ce qu'est «soulever». Dois-je d'abord comprendre les monades avant de comprendre ce qu'est un «ascenseur»? (Moi aussi, j'ignore complètement les monades :) Ou quelqu'un peut-il me l'expliquer avec des mots simples?
Je ne comprends pas ce qu'est «soulever». Dois-je d'abord comprendre les monades avant de comprendre ce qu'est un «ascenseur»? (Moi aussi, j'ignore complètement les monades :) Ou quelqu'un peut-il me l'expliquer avec des mots simples?
Réponses:
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 en quoi les ascenseurs sont une catégorie ou quelque chose).
En règle générale, vous avez un type de données avec un paramètre. Quelque chose comme
data Foo a = Foo { ...stuff here ...}
Supposons que vous trouviez que de nombreuses utilisations de Foo
types numériques prennent ( Int
, Double
etc.) et que vous continuiez à écrire du code qui déballe ces nombres, les ajoute ou les multiplie, puis les rembobine. Vous pouvez court-circuiter cela en écrivant une fois le code de déroulement 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 l' (+)
opérateur) et la transforme en fonction équivalente pour Foos.
Alors maintenant tu peux é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
. Alors plutôt que liftFoo1
tu écrirais
instance Functor Foo where
fmap f foo = ...
Si vous voulez vraiment une régularité complète, vous pouvez alors dire
liftFoo1 = fmap
Si vous pouvez en faire Foo
un foncteur, vous pouvez peut-être en faire un foncteur applicatif. En fait, si vous pouvez écrire, liftFoo2
l'instance applicative ressemble à ceci:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
L' (<*>)
opérateur de 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 mettre en œuvre, liftFoo2
vous pouvez l'écrire en termes de cela. Ou vous pouvez l'implémenter directement et ne pas vous embêter liftFoo2
, car le Control.Applicative
module comprend
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
et de même il y a 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é à l'argument suivant en utilisant (<*>)
, et ainsi de suite. Alors maintenant, au lieu d'avoir une fonction d'ascenseur pour chaque arité, vous avez juste une guirlande d'applicatifs.
lift id == id
et lift (f . g) == (lift f) . (lift g)
.
id
et .
sont respectivement la flèche d'identité et la composition de flèches d'une certaine catégorie. Habituellement, quand on parle de Haskell, la catégorie en question est "Hask", dont les flèches sont des fonctions Haskell (en d'autres termes, id
et .
font référence aux fonctions Haskell que vous connaissez et aimez).
instance Functor Foo
, non instance Foo Functor
, non? Je modifierais moi-même mais je ne suis pas sûr à 100%.
Paul et yairchu sont tous deux de bonnes explications.
Je voudrais ajouter que la fonction levée peut avoir un nombre arbitraire d'arguments et qu'ils ne doivent pas nécessairement être 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 1 argument est capturée dans la classe de type Functor
et l'opération de levage est appelée fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Notez la similitude avec liftFoo1
le type de. En fait, si c'est le cas liftFoo1
, vous pouvez créer Foo
une instance de Functor
:
instance Functor Foo where
fmap = liftFoo1
De plus, la généralisation du levage à un nombre arbitraire d'arguments est appelée style applicatif . Ne vous donnez pas la peine de plonger dans cela jusqu'à ce que vous ayez saisi la levée des fonctions avec un nombre fixe d'arguments. Mais quand vous le faites, Learn you a 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 jusqu'au chapitre droit de ce document).
J'espère que cela t'aides!
Commençons par un exemple (un espace blanc est ajouté pour une présentation plus claire):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
transforme une fonction de types simples en une fonction des mêmes types enveloppée dans unApplicative
, comme des listes IO
, etc.
Un autre ascenseur commun est lift
de Control.Monad.Trans
. Il transforme une action monadique d'une monade en une action d'une monade transformée.
En général, "lever" lève une fonction / action dans un type "enveloppé" (de sorte que la fonction d'origine se met au travail "sous les enveloppes").
La meilleure façon de comprendre cela, et les monades etc., et de comprendre pourquoi elles sont utiles, est probablement de les coder et de les utiliser. S'il y a quelque chose que vous avez précédemment codé et que vous soupçonnez de pouvoir en bénéficier (c'est-à-dire que cela raccourcira le code, etc.), essayez-le et vous comprendrez facilement le concept.
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 œil à http://haskell.org/haskellwiki/Lifting
Selon ce tutoriel brillant , un foncteur est un conteneur (comme Maybe<a>
, List<a>
ou Tree<a>
qui peut 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 je 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 s'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, la fourniture a->b
seule transforme cette fonction au niveau de l'élément en fonction functor<a> -> functor<b>
qui opère sur les conteneurs. C'est ce qu'on appelle le levagede la fonction. Parce que le conteneur est également appelé un foncteur , les Functors plutôt que les Monades sont un prérequis pour le levage. Les monades sont en quelque sorte «parallèles» au levage. Les deux s'appuient sur la notion de Functor et font f<a> -> f<b>
. La différence est que le levage utilise a->b
pour la conversion alors que Monad oblige l'utilisateur à définir a -> f<b>
.
r
à un type (utilisons c
pour la variété), sont des Functors. Ils ne «contiennent» aucun c
. Dans ce cas, fmap est une composition de fonction, prenant une a -> b
fonction et une r -> a
autre, pour vous donner une nouvelle r -> b
fonction. Toujours pas de conteneurs Et si je pouvais, je le marquerais à nouveau pour la dernière phrase.
fmap
est une fonction, et n'attend rien; Le "container" étant un Functor, c'est tout l'intérêt du levage. En outre, les Monades sont, si quelque chose, une double idée de la levée: une Monade vous permet d'utiliser quelque chose qui a été soulevé un certain nombre de fois positif, comme s'il n'avait été soulevé qu'une seule fois - c'est mieux connu sous le nom d' aplatissement .
To wait
, to expect
, to anticipate
sont les synonymes. En disant «fonction attend», je voulais dire «fonction anticipe».
b = 5 : a
et f 0 = 55
f n = g n
, les deux impliquent une pseudo-mutation du "conteneur". Aussi le fait que les listes sont généralement stockées complètement en mémoire alors que les fonctions sont généralement stockées sous forme de calcul. Mais la mémorisation / les listes monorphes qui ne sont pas stockées entre les appels cassent la merde de cette idée.