Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reuse a type variable in an inner type declaration

Tags:

haskell

As part of my Haskell learning process, I like to explicitly type out the type declarations for functions. I would like to be able to do so for functions defined in a where clause, but I don't know how to specify, that a type variable in a where clause should denote the same type as some type variable in the outer type declaration. For instance, the following code:

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: a -> a
    bar a = f a

yields this error:

src\Test.hs:7:14:
    Couldn't match expected type `a' against inferred type `a1'
      `a' is a rigid type variable bound by
          the type signature for `foo' at src\Test.hs:3:8
      `a1' is a rigid type variable bound by
           the type signature for `bar' at src\Test.hs:6:11
    In the first argument of `f', namely `a'
    In the expression: f a
    In the definition of `bar': bar a = f a

How can I express that the first argument to bar should be of the same type as the second argument to foo, so that I can apply f to it?

Thanks.

like image 262
Boris Avatar asked Mar 29 '11 17:03

Boris


2 Answers

I think you can do this in general with ScopedTypeVariables which GHC supports. This certainly compiles:

{-# LANGUAGE ScopedTypeVariables #-}
foo :: forall a. (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: a -> a
    bar a = f a

Note the "forall a."

like image 85
Chris Kuklewicz Avatar answered Oct 23 '22 10:10

Chris Kuklewicz


There is another workaround. Instead of referencing f within the inner function bar, extend bar to accept f as a first argument and use partial application in the parent.

foo :: (a -> a) -> a -> a
foo f arg = (bar f) arg
  where
    bar :: (a -> a) -> a -> a
    bar f a = f a

It does not require ScopedTypeVariables or explicit type checking code as the other answers.

Explanation

For clarity let's change the type parameter in bar to b and also rename its argument.

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    bar :: b -> b
    bar x = f x

Haskell complains because bar is annotated as b -> b (for any arbitrary type b), but f x is attempting to apply an argument of type b to a function of type a -> a (for a specific, bound a). In other words, the inner function is not as general as its type annotation advertises.

Passing f to bar means that for the expression (bar f), the type variable b is bound to the same type as a.

Even simpler

And finally, without changing anything else, if you're willing to omit the type signature for the inner function bar, Haskell will infer its type exactly the way you want. That is, since bar applies f from from the parent function foo, the type of bar will reuse the type parameter a from the type of foo.

foo :: (a -> a) -> a -> a
foo f arg = bar arg
  where
    -- Type: bar :: a -> a
    bar a = f a
like image 26
mxxk Avatar answered Oct 23 '22 11:10

mxxk