42

In my free time I'm learning Haskell, so this is a beginner question.

In my readings I came across an example illustrating how Either a is made an instance of Functor:

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

Now, I'm trying to understand why the implementation maps in the case of a Right value constructor, but doesn't in the case of a Left?

Here is my understanding:

First let me rewrite the above instance as

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

Now:

  1. I know that fmap :: (c -> d) -> f c -> f d

  2. if we substitute f with Either a we get fmap :: (c -> d) -> Either a c -> Either a d

  3. the type of Right (g x) is Either a (g x), and the type of g x is d, so we have that the type of Right (g x) is Either a d, which is what we expect from fmap (see 2. above)

  4. now, if we look at Left (g x) we can use the same reasoning to say that its type is Either (g x) b, that is Either d b, which is not what we expect from fmap (see 2. above): the d should be the second parameter, not the first! So we can't map over Left.

Is my reasoning correct?

Petr
  • 60,177
  • 8
  • 136
  • 295
MarcoS
  • 12,788
  • 5
  • 36
  • 61
  • 17
    Either is perhaps more obviously a Bifunctor than a Functor - a Bifunctor has an operation bimap - bimap :: (a -> m) -> (b -> n) -> f a b -> f m n. This gives you mapping on both Left and Right cases. Haskell's standard library doesn't have a Bifunctor class, this is because there are a lot less Bifunctors than Functors "in the wild" although it is handy for Either and pair (a,b). – stephen tetley Mar 04 '11 at 16:12
  • 1
    In 3, you use (g x) as part of a type, but it is a value. It appears you meant to write the `typeof (g x)` there, and not `(g x)` itself. – Peaker Mar 05 '11 at 23:20
  • @stephen tetley: that's interesting! Thanks – MarcoS Mar 07 '11 at 08:41
  • @stephentetley I know you wrote this a while ago, but now Bifunctor is in standard library since base-4.8.0.0: https://hackage.haskell.org/package/base-4.8.0.0/docs/Data-Bifunctor.html – modular May 06 '16 at 10:38

4 Answers4

24

This is right. There is also another quite important reason for this behavior: You can think of Either a b as a computation, that may succeed and return b or fail with an error message a. (This is also, how the monad instance works). So it's only natural, that the functor instance won't touch the Left values, since you want to map over the computation, if it fails, there's nothing to manipulate.

fuz
  • 76,641
  • 24
  • 165
  • 316
  • I haven't learned about monads yet :) I've read that `Either a b` is used in the way that you describe, and that `a` represents the error message: but this is just a convention, right? – MarcoS Mar 04 '11 at 15:54
  • @MarcoS: correct, this is only a convention. Either could well be interpreted as a disjunction: a value of type Either a b contains value of type a or value of type b but not both. – Artyom Shalkhakov Mar 04 '11 at 16:42
  • 2
    In the individual case -- "Either String String" "Either String Char", "Either String Int" etc.-- this is so, of course; there is no difference between the right and left types. But as a functor you can fmap over - as (Either String), in those examples - there is a complete asymmetry. The type that enters into the concrete Functor instance (here `String`) has to be representing something general that can be found in all ordinary computation with any type (which happens on the right, via `fmap`) -- so an 'error' interpretation, while hardly the only one, is not an accident. – applicative Mar 05 '11 at 04:37
14

Your account is right of course. Maybe the reason why we have a difficulty with instances like this is that we are really defining infinitely many functor instances at once -- one for each possible Left type. But a Functor instance is a systematic way of operating on the infinitely many types in the system. So we are defining infinitely many ways of systematically operating on the infinitely many types in the system. The instance involves generality in two ways.

If you take it by stages, though, maybe it's not so strange. The first of these types is a longwinded version of Maybe using the unit type () and its only legitimate value ():

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

Here we might get tired and make an abstraction:

data Unless a b    = Mere a     | Genuine b

Now we make our Functor instances, unproblematically, the first looking a lot like the instance for 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)

But, again, why bother, let's make the abstraction:

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

The Mere a terms aren't touched, as the (), String and Int values weren't touched.

applicative
  • 7,791
  • 32
  • 38
  • Thanks, this is also a very nice explanation. However, I accepted FUZxxl's post as answer because because besides confirming my reasoning, it gives me also an interesting hint on interpreting `Either a b` as computation (monad) – MarcoS Mar 07 '11 at 08:53
  • This is a great answer. I particularly appreciate your first paragraph which explains the double generality. – Ludovic Kuty Nov 02 '18 at 19:12
5

As others mentioned, Either type is a functor in its both arguments. But in Haskell we are able to (directly) define only functors in a type's last arguments. In cases like this, we can get around the limitation by using newtypes:

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

So we have constructor FlipEither :: Either a b -> FlipEither b a that wraps an Either into our newtype with swapped type arguments. And we have dectructor unFlipEither :: FlipEither b a -> Either a b that unwraps it back. Now we can define a functor instance in FlipEither's last argument, which is actually Either's first argument:

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

Notice that if we forget FlipEither for a while we get just the definition of Functor for Either, just with Left/Right swapped. And now, whenever we need a Functor instance in Either's first type argument, we can wrap the value into FlipEither and unwrap it afterward. For example:

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

Update: Have a look at Data.Bifunctor, of which Either and (,) are instances of. Each bifunctor has two arguments and is a functor in each of them. This is reflected in Bifunctor's methods first and second.

The definition of Bifunctor of Either is very 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
Petr
  • 60,177
  • 8
  • 136
  • 295
1

Now, I'm trying to understand why the implementation maps in the case of a Right value constructor, but doesn't in the case of a Left?

Plug in here and it might make sense.

Assume a = String (an error message) You apply Either a to an Float.

So you have an f: Float -> Integer say for example roundoff.

(Either String) (Float) = Either String Float.

now (fmap f):: Either String Float -> Either String Int So what are you going to do with f? f doesn't have a clue what to do with strings so you can't do anything there. That is obviously the only thing you can act on are the right values while leaving the left values unchanged.

In other words Either a is a functor because there is such an obvious fmap given by:

  • for Right values apply f
  • for Left values do nothing
jbolden1517
  • 329
  • 1
  • 3