2011-03-04 4 views
31

Pendant mon temps libre j'apprends Haskell, c'est donc une question débutant.Comprendre comment Either est une instance de Functor

Dans mes lectures, je suis tombé sur un exemple illustrant comment Either a est fait une instance de Functor:

instance Functor (Either a) where 
    fmap f (Right x) = Right (f x) 
    fmap f (Left x) = Left x 

Maintenant, je suis en train de comprendre pourquoi les cartes de mise en œuvre dans le cas d'un constructeur de valeur Right, mais ne pas dans le cas d'un Left?

Voici ma compréhension:

Tout d'abord permettez-moi de réécrire l'exemple ci-dessus comme

instance Functor (Either a) where 
    fmap g (Right x) = Right (g x) 
    fmap g (Left x) = Left x 

maintenant:

  1. Je sais que fmap :: (c -> d) -> f c -> f d

  2. si nous substituer f avec Either a nous obtenons fmap :: (c -> d) -> Either a c -> Either a d

  3. le type de Right (g x) est Either a (g x), et le type de g x est d, nous avons donc que le type de Right (g x) est Either a d, qui est ce que nous attendons de fmap (voir 2 ci-dessus)

  4. maintenant, si nous regardons Left (g x) nous pouvons utiliser le même raisonnement pour dire que son type est Either (g x) b, qui est Either d b, ce qui est pas ce que nous attendons de fmap (voir 2 ci-dessus): le d devrait être le second paramètre pas le premier! Donc, nous ne pouvons pas cartographier Left.

Mon raisonnement est-il correct?

+11

Ou est peut-être plus évidemment bifoncteur qu'un Functor - un bifoncteur a une bimap opération - bimap :: (a -> m) -> (b -> n) -> fab -> fm n. Cela vous donne un mappage sur les deux cas de gauche et de droite. La bibliothèque standard de Haskell n'a pas de classe Bifunctor, c'est parce qu'il y a beaucoup moins de Bifoncteurs que de Functors "dans la nature" bien que ce soit pratique pour Either et pair (a, b). –

+1

En 3, vous utilisez (g x) en tant que partie d'un type, mais c'est une valeur. Il semble que vous vouliez écrire le 'typeof (g x)' là, et non '(g x)' lui-même. – Peaker

+0

@stephen tetley: c'est intéressant! Merci – MarcoS

Répondre

20

Ceci est vrai. Il y a aussi une autre raison assez importante pour ce comportement: Vous pouvez penser à Either a b comme un calcul, qui peut réussir et retourner b ou échouer avec un message d'erreur a. (Ceci est aussi, comment fonctionne l'instance de monade). Il est donc naturel que l'instance de foncteur ne touche pas les valeurs Left, puisque vous voulez faire une correspondance sur le calcul, si elle échoue, il n'y a rien à manipuler.

+0

Je n'ai pas encore entendu parler des monades :) J'ai lu que 'soit ab 'est utilisé de la façon que vous décrivez, et que' a' représente le message d'erreur: mais c'est juste une convention, n'est-ce pas? – MarcoS

+0

@MarcoS: c'est correct, c'est seulement une convention. L'une ou l'autre pourrait bien être interprétée comme une disjonction: une valeur de type Soit a b contient une valeur de type a, soit une valeur de type b mais pas les deux. –

+0

Dans le cas individuel - "Sither String String" "Soit String Char", "Sither String Int", etc - c'est ainsi, bien sûr; il n'y a pas de différence entre les types droit et gauche. Mais en tant que foncteur, vous pouvez fmap over - as (dans l'un ou l'autre String), dans ces exemples - il y a une asymétrie complète. Le type qui entre dans l'instance concrète de Functor (ici 'String') doit représenter quelque chose de général qui peut être trouvé dans tout calcul ordinaire avec n'importe quel type (ce qui arrive à droite, via' fmap') - donc une 'erreur' «L'interprétation, bien que difficilement la seule, n'est pas un accident. – applicative

8

Votre compte est correct bien sûr. Peut-être que la raison pour laquelle nous avons une difficulté avec des instances comme celle-ci est que nous définissons réellement un nombre infini d'instances de foncteurs à la fois - une pour chaque type possible de . Mais une instance de Functor est une manière systématique d'opérer sur l'infinité de types dans le système. Nous définissons donc une infinité de façons de fonctionner systématiquement sur les types infiniment nombreux dans le système. L'instance implique une généralité de deux manières.

Si vous le prenez par étapes, cependant, ce n'est peut-être pas si étrange.Le premier de ces types est une version longwinded de Maybe en utilisant le type d'unité () et sa seule valeur légitime ():

data MightBe b  = Nope() | Yep b 
data UnlessError b = Bad String | Good b 
data ElseInt b  = Else Int | Value b 

Ici, nous pourrions être fatigué et faire abstraction:

data Unless a b = Mere a  | Genuine b 

Maintenant, nous faisons nos instances de foncteurs, sans problèmes, la première recherche un peu comme l'instance pour Maybe:

instance Functor MightBe where 
    fmap f (Nope()) = Nope() -- compare with Nothing 
    fmap f (Yep x) = Yep (f x) -- compare with Just (f x) 

instance Functor UnlessError where 
    fmap f (Bad str) = Bad str -- a more informative Nothing 
    fmap f (Good x) = Good (f x) 

instance Functor ElseInt where 
    fmap f (Else n) = Else n 
    fmap f (Value b) = Value (f b) 

Mais, encore une fois, pourquoi la peine, nous allons faire l'abstraction:

instance Functor (Unless a) where 
    fmap f (Mere a) = Mere a 
    fmap f (Genuine x) = Genuine (f x) 

Les Mere a termes ne sont pas touchés, comme les (), String et Int valeurs ne sont pas touchés.

+0

Merci, c'est aussi une très bonne explication. Cependant, j'ai accepté le post de FUZxxl comme réponse parce que, en plus de confirmer mon raisonnement, cela me donne aussi un indice intéressant sur l'interprétation de 'Soit a b' comme calcul (monad) – MarcoS

1

Maintenant, je suis en train de comprendre pourquoi les cartes de mise en œuvre dans le cas d'un constructeur de bonne valeur , mais ne pas dans le cas d'une gauche?

Branchez ici et cela pourrait avoir du sens.

On suppose a = String (un message d'erreur) Vous appliquez Soit un à un flotteur.

Vous avez donc un f: Float -> Entier dire par exemple roundoff.

(Soit String) (float) = Soit cordes flotteur.

maintenant (fmap f) :: Soit String Float -> Soit String Int Alors, qu'allez-vous faire avec f? Je n'ai pas la moindre idée de ce qu'il faut faire avec les cordes, donc vous ne pouvez rien y faire. C'est évidemment la seule chose que vous pouvez agir sur les bonnes valeurs sont tout en laissant les valeurs inchangées.

En d'autres termes Soit un est un foncteur parce qu'il ya une telle fmap évidente donnée par:

  • pour les valeurs droite appliquent f
  • pour les valeurs de gauche ne rien
4

Comme d'autres ont mentionné , Either type est un foncteur dans ses deux arguments. Mais dans Haskell, nous ne pouvons (directement) définir que des foncteurs dans les derniers arguments d'un type. Dans les cas comme celui-ci, on peut contourner la limitation en utilisant newtype s:

newtype FlipEither b a = FlipEither { unFlipEither :: Either a b } 

Nous avons donc constructeur FlipEither :: Either a b -> FlipEither b a qui enveloppe un Either dans nos newtype avec des arguments de type permutées. Et nous avons le dectructor unFlipEither :: FlipEither b a -> Either a b qui le déballe.Maintenant, nous pouvons définir une instance de foncteur dans FlipEither « dernier argument, qui est en fait Either » premier argument:

instance Functor (FlipEither b) where 
    fmap f (FlipEither (Left x)) = FlipEither (Left (f x)) 
    fmap f (FlipEither (Right x)) = FlipEither (Right x) 

Notez que si nous oublions FlipEither pendant un certain temps, nous obtenons simplement la définition de Functor pour Either, juste avec Left/Right échangé. Et maintenant, chaque fois que nous avons besoin d'une instance Functor dans le premier argument de type Either, nous pouvons envelopper la valeur dans FlipEither et la déplier par la suite. Par exemple:

fmapE2 :: (a -> b) -> Either a c -> Either b c 
fmapE2 f = unFlipEither . fmap f . FlipEither 

Mise à jour: Jetez un oeil à Data.Bifunctor, dont Either et (,) sont des instances de. Chaque bifunctor a deux arguments et est un foncteur dans chacun d'eux. Cela se reflète dans les méthodes Bifunctorfirst et second.

La définition de Bifunctor de Either est très symetric:

instance Bifunctor Either where 
    bimap f _ (Left a) = Left (f a) 
    bimap _ g (Right b) = Right (g b) 

    first f = bimap f id 

    second f = bimap id f 
Questions connexes