Boris
Boris

Reputation: 5176

How to reuse a type variable in an inner type declaration

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.

Upvotes: 18

Views: 1699

Answers (4)

Chris Kuklewicz
Chris Kuklewicz

Reputation: 8153

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."

Upvotes: 18

mxxk
mxxk

Reputation: 10264

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

Upvotes: 8

sastanin
sastanin

Reputation: 41541

This answer to another question shows a trick to use if you do not want to use ScopedTypeVariables extension.

Upvotes: 3

Related Questions