Why is a kind annotation suggested for V1 and V3, but notV2?
-- Suggests annotation:
-- type V1 :: forall k. (k -> Type) -> Type
type V1 c =
{ foo :: forall a. c a -> c a
}
-- No annotation suggested
type V2 c =
{ foo :: forall a. c a -> c a
, bar :: forall a. c a -> a
}
-- Suggests annotation:
-- type V3 :: forall k. (k -> Type) -> Type
type V3 c =
{ foo :: forall a. c a -> c a
, bar :: forall a. c a -> Int
}
It warns whenever it infers a polymorphic kind. V2 does not suggest anything because the signature of bar fixes a :: Type since (->) :: Type -> Type -> Type. In the others, a is otherwise unconstrained and can be generalized. For example, c could be Record and a :: Row Type.
Thanks for the explanation. I realized I had a few gaps in understanding. Here are some additional notes I made while reasoning through what’s going on in case anyone else is similarly confused.
An arrow -> in a type signature is the Function data constructor, which has a “kind” signature of Type -> Type -> Type. Note that these kind signature arrows are different than type signature arrows. Only the former means Function.
So then if we look at V2's bar :: forall a. c a -> a, we can fill-in the following:
Type -> Type -> Type
(c a) a Function (c a) a
Looking at the second Type, we see that a has kind Type.
Looking at the first Type, we see that c a has kind Type, and since a was earlier determined to have kind Type, then c must have kind Type -> Type. This kind signature is straightforward (no kind polymorphism), and so the compiler doesn’t nag us for annotation. But we could still include the kind annotation if we wanted to: type V2 :: (Type -> Type) -> Type.
In V1 or V3, we can’t nail down the kinds of any a to simply Type. These as could be a variety of things, such as Type -> Type, Type -> Type -> ... -> Type, Row Type, etc.
Since c must accept any of these kinds of a, we use a kind variable k to indicate this “kind polymorphism” and describe the kind of c as forall k. (k -> Type). The compiler wants us to annotate declarations involving polymorphic kinds.
I don’t know if this is accurate, or at least I’m not totally sure what you mean to convey. I’d think of it like:
PureScript is two “languages”: The term-level language and the type-level language.
The type-level language is used to express the constraints of the term-level language.
The same type-level language is also used to express the constraints of itself (the type-level language).
The term-level language has reducible computation (lambdas) and constructors, both of which are typed using Function a b or a -> b. That is, data Foo = Bar Int introduces a constructor Bar :: Int -> Foo, which is typed using ->, but this doesn’t “compute” anything. It’s just symbolic data.
The type-level language only has constructors (what we call type-constructors). These constructors are still typed using Function or ->.