5

I am implementing Algorithm W for a toy language. I came across a case that I imagined would type check, but doesn't. I tried the same in Haskell, and to my surprise it didn't work there either.

> (\id -> id id 'a') (\x -> x)
Couldn't match type ‘Char -> t’ with ‘Char’
Expected type: Char -> t
Actual type: (Char -> t) -> Char -> t

I assumed that id would be polymorphic, but it doesn't seem to be. Note that this example works if id is defined using let instead of passed as an argument:

let id x = x in id id 'a'
'a'
:: Char

Which makes sense when looking at the inference rules for Algorithm W, since it has a generalization rule for let expressions.

But I wonder if there is any reason for this? Couldn't the function parameter be generalized as well so it can be used polymorphically?

waxwing
  • 18,547
  • 8
  • 66
  • 82
  • 4
    it's not `(\x -> x)` that is or is not polymorphic, it's the _binding_ for `id`. `let id = \x -> x in id id 1` works. it's the same `\x -> x`, but _`let`'s_ bindings get polymorphic types. it's known as "`let` polymorphism". – Will Ness Jul 22 '21 at 11:34
  • @WillNess good clarification. I didn't realize that until the accepted answer was posted. I'll update the question accordingly. – waxwing Jul 22 '21 at 11:37

1 Answers1

4

The problem with generalizing lambda-bound variables is that it requires higher-rank polymorphism. Take your example:

(\id -> id id 'a')

If the type of id here is forall a. a -> a, then the type of the whole lambda expression must be (forall a. a -> a) -> Char, which is a rank 2 type.

Besides that technical point there is also an argument that higher rank types are very uncommon, so instead of inferring a very uncommon type it might be more likely that the user has made a mistake.

Noughtmare
  • 9,410
  • 1
  • 12
  • 38
  • 1
    This is where being explicit about invisible arguments becomes is important, the identity functions `id id 'a'` are applied at two different types `id @(Char -> Char) (id @Char) 'a'`. The *@type* syntax is enabled with `TypeApplications` and it allows overriding the visibility of type arguments. If you wanted to abstract `id` out no monomorphic type will suffice as it must be valid as `(Char -> Char) -> (Char -> Char)` as well as `Char -> Char`. The solution is to explicitly give `id` a higher-rank type: `\(id :: forall a. a -> a) -> id id 'a'` like Noughtmare said – Iceland_jack Jul 22 '21 at 12:58