4

Let's say I have the following type signature:

someFunction :: (Eq a, Eq b) => a -> b

With implementation:

someFunction x = (2 :: Int)

(Don't look in to it too far, it's just an example).

My understanding of the signature is that "someFunction takes an argument that is an instance of the Eq typeclass, and returns a value (that can be of a different type) that is an instance of the Eq typeclass". Int is an instance of Eq, so why does GHC get upset about this implementation?

The error makes it obvious enough:

Couldn't match expected type ‘b’ with actual type ‘Int’
     ‘b’ is a rigid type variable bound by
       the type signature for:
         someFunction :: forall a b. (Eq a, Eq b) => a -> b

I guess what I don't understand is the requirement that it work "forall" b. Any code using this function should only rely on the fact that b is an instance of Eq, right? In my head, the implementation does match the signature. What about my implementation is breaking the expectations of this signature?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Cameron Ball
  • 4,048
  • 6
  • 25
  • 34
  • 3
    Because your signature says "I can return *any* `b`, as long as it is `Eq b`), but your implementation is clearly only returning `Int`s. – Willem Van Onsem Oct 22 '18 at 08:06
  • I suppose I'm misunderstanding the value of saying "i can return any b". – Cameron Ball Oct 22 '18 at 08:07
  • 1
    You might also like [this question](https://stackoverflow.com/q/42820603/791604); there I describe types as a sort of two-player game, and I think it's a good way to summarize what's going on here. – Daniel Wagner Oct 22 '18 at 12:40

1 Answers1

10

No, your type signature, which is actually

forall a b. (Eq a, Eq b) => a -> b

means that your function must be callable with any types a and b, as determined by the call site, as long as both are instances of Eq.

It is not your function that decides what type to return. It's your function's use that determines that.

So you should be able to write

    let { i :: Int; i = 1;
          n :: Integer; y :: Double;
          n = foo i;   -- foo :: Int -> Integer
          y = foo i    -- foo :: Int -> Double
        }

and as you can see, the only implementation for your function is no implementation:

foo _ = x where {x = x}

because you have no way to produce a value of any type that's demanded of you. That type can be anything, and you have no way of knowing anything about it.


By the way other typeclasses might actually allow you to define something here, like

foo :: (Enum a, Enum b, Bounded a, Bounded b) => a -> b 
foo a = snd . last $ zip [minBound .. a] (cycle [minBound ..])

I'm not saying it's a sensible definition, just that it is possible:

> foo () :: Bool
False

> foo True :: Int
-9223372036854775807

> foo (0 :: Int) :: Bool
Interrupted.

It is probably a common misconception for programmers coming from the more usual languages to think that foo :: (Eq a) => a means "I get to define foo to return any type I want, as long as it is in Eq". It doesn't. :)

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 1
    "It is not your function that decides what type to return. It's your function's use that determines that." - that statement helps a lot. Thanks. Getting my head around parametric polymorphism is a challenge sometimes. – Cameron Ball Oct 22 '18 at 08:31
  • 5
    @CameronBall I think it helps a lot visualizing the implicit type arguments. For instance, when we write `id True` GHC internally expands this to `id @Bool True`, where the `@Bool` part chooses the type `a` in `id :: forall a. a->a`. Essentially, polymorphic functions are functions with one or more "type" arguments, which need to be passed with the special `@T` syntax, or omitted so that type inference can fill them for us. As an exercise, you can try to add all the `@T`s on your own and see what's under the hood (turn on the `TypeApplications` extension first). – chi Oct 22 '18 at 08:45