[Haskell-beginners] type class functions as function arguments

Karl Voelker ktvoelker at gmail.com
Fri Sep 14 06:16:56 CEST 2012

```On Thu, Sep 13, 2012 at 8:23 PM, Christopher Howard <
christopher.howard at frigidcode.com> wrote:

> code:
> --------
> gCore :: Num a => (a -> a -> a) -> (Integer, Double)
> gCore f = (f 1 2, f 2.0 3.0)
> --------
>
> The resulting error is:
>     Couldn't match type `Double' with `Integer'
>     In the return type of a call of `f'
>     In the expression: f 1 2
>     In the expression: (f 1 2, f 2.0 3.0)
>

In standard Haskell, when you write a type signature, there's an implicit
universal quantification of all its type variables:

gCore :: forall a. (Num a => (a -> a -> a) -> (Integer, Double))

The problem comes from the parentheses. You have to get past the forall,
and you can only do that by picking a value for the type variable. Another
way of looking at it is that you aren't actually required to have a
function (a -> a -> a) that works for *all* types a in Num; it only has to
work for whatever type you happened to choose for a. In this case, that's
not useful to you, since (+) is defined for all types a in Num, but
according to this type signature, the caller is able to pick a type a = Foo
and then provide a function of type Foo -> Foo -> Foo.

To get what you want, you need this type signature instead:

gCore :: (forall a. (Num a => (a -> a -> a))) -> (Integer, Double)

Since the forall only covers the type of the parameter, not gCore, the body
of gCore isn't committed to a particular value of a. You only have to
commit to a value of a when using the parameter. This also means that the
person calling gCore has to pass in a function that works for all types a
in Num, rather than just for one, because when they pass that parameter,
the value of a hasn't been chosen yet, since we didn't have to go inside
that forall to get to the point of passing that parameter.

This is not standard Haskell, but it is possible with a language extension
supported by GHC called RankNTypes. (Use -XRankNTypes when running ghc or
ghci.) There are downsides to using this feature which I am not qualified
to fully explain, but the most common problem you'll find is that type
inference fails and an explicit signature is required. (There is also an
extension called Rank2Types which covers this particular case, since the
forall is only one level deeper than normally allowed.)

-Karl
-------------- next part --------------
An HTML attachment was scrubbed...