6

When making my custom Either, and Functor, just to understand clearer types and typeclasses, I found the following situation:

Functor

module Functor (Functor, fmap) where

import Prelude hiding(Functor, fmap)

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Either

module Either(Either(..)) where
import Prelude hiding(Either(..), Functor, fmap)

data Either a b = Left a | Right b deriving(Show)

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

The code showed above compiles fine but, if I change it to use id, it doesn't compile:

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

Why?? Did I miss something? The following code also doesn't work:

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

... This seems to me very strange because the code showed below compiles:

data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

data Point = Point Float Float deriving (Show)

test :: Shape -> String
test (Circle _ x) = show x
test all@(Rectangle _ x) = show all ++ " - "++ show x

Thank you in advance

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
FtheBuilder
  • 1,410
  • 12
  • 19
  • 6
    The first case with `id` is very well explained in [this question](http://stackoverflow.com/questions/8745597/defining-a-function-by-equations-with-different-number-of-arguments). By the way, be sure to take note of and mention in your questions the specific errors you get - it makes it easier both for you and for answerers to find answers. – duplode Jul 22 '15 at 02:39
  • 1
    Great point, I will keep it – FtheBuilder Jul 22 '15 at 02:42
  • If you're interested in point-free expressions, you might think this definition cute: `fmap = either id` – dfeuer Oct 04 '15 at 17:09

3 Answers3

7

Let's look at the type of fmap specialized for the Either functor:

fmap :: (a -> b) -> Either e a -> Either e b

As we can see from this, in fmap f all@(Left _), the type of all is Either e a. This doesn't match the intended result type Either e b prescribed by the signature of fmap, so fmap f all@(Left _) = all is not well-typed.

Similarly for the case using id.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • I understood your answer partially, but I still think it is strange, because (correct me if I am wrong), returning `Left x` instead of `all` would also be of type `Either e a`, they are the same thing... I am trying to think the simplest way possible – FtheBuilder Jul 22 '15 at 02:52
  • 2
    No, you are wrong. If `x :: e`, then `Left x :: Either e c` for any choice of `c`. If you set `c ~ b`, you get `Left x :: Either e b`. – Cactus Jul 22 '15 at 02:53
  • 2
    @FtheBuilder `id` has type `a -> a`, the return type matches **exactly** the type of the argument. So it maps `Either a b` into an other `Either a b`. But the return type of `fmap` does **not** match exactly the second argument, even though one of its parameters doesn't change. Hence using simply `= id` wont work. Also using `all` doesn't work for the same reason: the argument has the wrong type. Haskell cannot simply generalize the type of the argument to allow what you want. – Bakuriu Jul 22 '15 at 05:30
  • @Bakuru, yes, it makes sense, but, what happens here: `let a = Left 'a'; let b = fmap (+3) a; a == b;` => this outputs `True`, well, doesn't it make sense they work for the same types? Sorry for repeating myself again and again, I just think it could be simpler, instead of having to construct `Left a` again... – FtheBuilder Jul 22 '15 at 12:29
  • @FtheBuilder: see my answer for how to not have to construct a `Left a` again. – Erik Kaplun Oct 05 '15 at 08:52
7

What you're trying to do boils down to this:

f :: Either a Bool -> Either a ()
f (Right _) = Right ()
f left = left

with error:

foo.hs:3:7:
    Couldn't match type ‘Bool’ with ‘()’
    Expected type: Either a ()
      Actual type: Either a Bool
    In the expression: left
    In an equation for ‘f’: f left = left
Failed, modules loaded: none.

left is bound to the function argument. So the type checker knows it's of type Either a Bool. Then it's used as the return value. We know from the type f :: Either a Bool -> Either a () that the return value must be of type Either a (). If left is a valid return value, it's type must match the return type of f. So Either a () must be equal to Either a Bool; it is not, so the type checker rejects the program.

In turn, it's basically the same problem as this:

λ let l = Left () :: Either () ()
l :: Either () ()

λ l
Left ()
it :: Either () ()

λ l :: Either () Bool

<interactive>:10:1:
    Couldn't match type ‘()’ with ‘Bool’
    Expected type: Either () Bool
      Actual type: Either () ()
    In the expression: l :: Either () Bool
    In an equation for ‘it’: it = l :: Either () Bool

We gave l a binding and a type, then tried to use it as a different type. That's invalid (and feeding it through id wouldn't change its type either). Even though Left () is also valid source code text for a value of type Either () Bool, that doesn't mean that a particular value known to be of type Either () () that could be defined with the source text Left () can be used as if it were of type Either () Bool.

If you had a polymorphic value, you could do this:

λ let l = Left ()
l :: Either () b

λ l :: Either () ()
Left ()
it :: Either () ()

λ l :: Either () Bool
Left ()
it :: Either () Bool

Note that the original l value here was polymorphic in b; it can be used as an Either () b for any b.

But your fmap case is subtly different. The function fmap is polymorphic in the b, but the value of its argument is "within the scope of the polymorphism"; at the point you have your argument the type b has been chosen to be some specific type by the caller of fmap, so it's "some unknown type that could by anything" rather than "any type I feel like choosing". There is no way to somehow turn a value of type Either a b into a value of type Either a c, so you have to extract the a value and then create an Either a c containing it.

Ben
  • 68,572
  • 20
  • 126
  • 174
  • Your explanation was really amazing, made me understand better how Haskell controls types. By the way, is there any motive which the Haskell makers didn't make it analyse the __given__ value, to find out whether it could be of the type of the return type? I think this approach is more straight forward, although I understood why the given program doesn't compile. Shouldn't Haskell be the most simple way possible for programmers? – FtheBuilder Jul 22 '15 at 12:10
  • 3
    @FtheBuilder Primarily because we use types to enforce invariants, and so we don't like when things are done behind our backs to our types. A comparable, if simpler, situation is how there is no implicit casting of numbers in Haskell. In any case, your implicit conversion would be of limited use, as it could only work when you pattern match on `Left`. There is no total function `Either a b -> Either a c` ("total" here is the technical term for "works in all cases"). – duplode Jul 22 '15 at 16:38
  • @duplode Could you please explain better this passage: "Primarily because we use types to enforce invariants, and so we don't like when things are done behind our backs to our types.", maybe saying the same thing with different words – FtheBuilder Jul 23 '15 at 13:50
  • @FtheBuilder The point is you can give very strong guarantees about how Haskell functions behave based *only* on types. That goes from knowing that for *any* `Functor` if `fmap` follows `fmap id = id` then can't do anything but change the values within the functor, to capturing very general concepts merely by clever choices of type classes to fill in a signature, and to designing complex libraries that can't be used in a wrong way without causing a type error. If types are used like that, implicit conversions can only lead to bugs and confusion. That's why you don't see them in Haskell. – duplode Jul 23 '15 at 14:10
0

In terms of explaining the type error, I have nothing to add to the previous two answers, however I'd like to mention that Left x :: Either t a is represented in memory the same way as Left x :: Either t b. What this means is that even though the type system does not let you use an Either t a in place of a value of type Either t b, for reasons already explained in perfect clarity by the other answers, you can "force" it through the type checker using unsafeCoerce:

import Unsafe.Coerce (unsafeCoerce)

instance Functor (Either t) where
  fmap f (Right a) = Right (f a)
  fmap f l         = unsafeCoerce l

And even though unsafeCoerce is generally regarded as something to avoid, if you know what you are doing, and you have a valid reason to do so, such as performance, unsafeCoerce can be useful in letting the compiler know you are sure the runtime value will match the expected structure.

In this case, without unsafeCoerce, and not considering any potential GHC optimizations, fmap f (Left x) = Left x would always construct a new but physically identical Left value, whereas the unsafeCoerce flavor would just return the original Left value with no additional memory allocations.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111