When I declare this newtype:
newtype ListScott a =
ListScott {
unconsScott :: (a -> ListScott a -> r) -> r -> r
}
which would define the hypothetical rank-2 type ListScott :: ((a -> ListScott a -> r) -> r -> r) -> ListScott a
, the compiler complains about r
not being in scope. Isn't it apparent from the type signature that I want to pass a first class polymorphic function to ListScott
?
Why do I need an explicit type quantifier for r
for cases like this?
I am not a type theorist and have probably overlooked something...
This is a question of programming language design. It could be inferred in the way you suggest, but I argue that it is a bad idea.
I don't think that we can obviously tell that much from this definition.
Universal or existential? Conflict with GADT notation
Here's something we can write with the
GADTs
extension:Here
r
is quantified existentially in theunconsScott
field, so the constructor has the first type below:Inference disables error detection
What if
r
is instead meant to be a parameter toListScott
, but we simply forgot to add it? I believe that is a reasonably probable mistake, because both the hypotheticalListScott r a
andListScott a
can serve as representations of lists in some ways. Then inference of binders would result in an erroneous type definition being accepted, and errors being reported elsewhere, once the type gets used (hopefully not too far, but that would still be worse than an error at the definition itself).Explicitness also prevents typos where a type constructor gets mistyped as a type variable:
There is not enough context in a type declaration alone to confidently guess the meaning of variables, thus we should be forced to bind variables properly.
Readability
Consider record of functions:
We have to look to the left of
=
to determine whetherr
is bound there, and if not we have to mentally insert binders in every field. I find that makes the first version hard to read because ther
variable in the two fields would actually not be under the same binder, but it certainly looks otherwise at a glance.Comparison to similar constructs
Note that something similar to what you suggested happens with type classes, which can be seen as a sort of record:
Most arguments above apply as well, and I would thus prefer to write that class as:
A similar thing could be said of local type annotations. However, top-level signatures are different:
That unambiguously means
id :: forall a. a -> a
, because there is no other level wherea
could be bound.