Functions don't just have types: They ARE Types. And Kinds. And Sorts. Help put a blown mind back together
Solution 1:
You touch so many interesting points in your question, so I am afraid this is going to be a long answer :)
Kind of (->)
The kind of (->)
is * -> * -> *
, if we disregard the boxity GHC
inserts. But there is no circularity going on, the ->
s in the
kind of (->)
are kind arrows, not function arrows. Indeed, to
distinguish them kind arrows could be written as (=>)
, and then
the kind of (->)
is * => * => *
.
We can regard (->)
as a type constructor, or maybe rather a type
operator. Similarly, (=>)
could be seen as a kind operator, and
as you suggest in your question we need to go one 'level' up. We
return to this later in the section Beyond Kinds, but first:
How the situation looks in a dependently typed language
You ask how the type signature would look for a function that takes a value and returns a type. This is impossible to do in Haskell: functions cannot return types! You can simulate this behaviour using type classes and type families, but let us for illustration change language to the dependently typed language Agda. This is a language with similar syntax as Haskell where juggling types together with values is second nature.
To have something to work with, we define a data type of natural numbers, for convenience in unary representation as in Peano Arithmetic. Data types are written in GADT style:
data Nat : Set where
Zero : Nat
Succ : Nat -> Nat
Set is equivalent to * in Haskell, the "type" of all (small) types,
such as Natural numbers. This tells us that the type of Nat
is
Set
, whereas in Haskell, Nat would not have a type, it would have
a kind, namely *
. In Agda there are no kinds, but everything has
a type.
We can now write a function that takes a value and returns a type.
Below is a the function which takes a natural number n
and a type,
and makes iterates the List
constructor n
applied to this
type. (In Agda, [a]
is usually written List a
)
listOfLists : Nat -> Set -> Set
listOfLists Zero a = a
listOfLists (Succ n) a = List (listOfLists n a)
Some examples:
listOfLists Zero Bool = Bool
listOfLists (Succ Zero) Bool = List Bool
listOfLists (Succ (Succ Zero)) Bool = List (List Bool)
We can now make a map
function that operates on listsOfLists
.
We need to take a natural number that is the number of iterations
of the list constructor. The base cases are when the number is
Zero
, then listOfList
is just the identity and we apply the function.
The other is the empty list, and the empty list is returned.
The step case is a bit move involving: we apply mapN
to the head
of the list, but this has one layer less of nesting, and mapN
to the rest of the list.
mapN : {a b : Set} -> (a -> b) -> (n : Nat) ->
listOfLists n a -> listOfLists n b
mapN f Zero x = f x
mapN f (Succ n) [] = []
mapN f (Succ n) (x :: xs) = mapN f n x :: mapN f (Succ n) xs
In the type of mapN
, the Nat
argument is named n
, so the rest of
the type can depend on it. So this is an example of a type that
depends on a value.
As a side note, there are also two other named variables here,
namely the first arguments, a
and b
, of type Set
. Type
variables are implicitly universally quantified in Haskell, but
here we need to spell them out, and specify their type, namely
Set
. The brackets are there to make them invisible in the
definition, as they are always inferable from the other arguments.
Set is abstract
You ask what the constructors of (->)
are. One thing to point out
is that Set
(as well as *
in Haskell) is abstract: you cannot
pattern match on it. So this is illegal Agda:
cheating : Set -> Bool
cheating Nat = True
cheating _ = False
Again, you can simulate pattern matching on types constructors in
Haskell using type families, one canoical example is given on
Brent Yorgey's blog.
Can we define ->
in the Agda? Since we can return types from
functions, we can define an own version of ->
as follows:
_=>_ : Set -> Set -> Set
a => b = a -> b
(infix operators are written _=>_
rather than (=>)
) This
definition has very little content, and is very similar to doing a
type synonym in Haskell:
type Fun a b = a -> b
Beyond kinds: Turtles all the way down
As promised above, everything in Agda has a type, but then
the type of _=>_
must have a type! This touches your point
about sorts, which is, so to speak, one layer above Set (the kinds).
In Agda this is called Set1
:
FunType : Set1
FunType = Set -> Set -> Set
And in fact, there is a whole hierarchy of them! Set is the type of
"small" types: data types in haskell. But then we have Set1
,
Set2
, Set3
, and so on. Set1
is the type of types which mentions
Set
. This hierarchy is to avoid inconsistencies such as Girard's
paradox.
As noticed in your question, ->
is used for types and kinds in
Haskell, and the same notation is used for function space at all
levels in Agda. This must be regarded as a built in type operator,
and the constructors are lambda abstraction (or function
definitions). This hierarchy of types is similar to the setting in
System F omega, and more
information can be found in the later chapters of
Pierce's Types and Programming Languages.
Pure type systems
In Agda, types can depend on values, and functions can return types, as illustrated above, and we also had an hierarchy of types. Systematic investigation of different systems of the lambda calculi is investigated in more detail in Pure Type Systems. A good reference is Lambda Calculi with Types by Barendregt, where PTS are introduced on page 96, and many examples on page 99 and onwards. You can also read more about the lambda cube there.
Solution 2:
Firstly, the ?? -> ? -> *
kind is a GHC-specific extension. The ?
and ??
are just there to deal with unboxed types, which behave differently from just *
(which has to be boxed, as far as I know). So ??
can be any normal type or an unboxed type (e.g. Int#
); ?
can be either of those or an unboxed tuple. There is more information here: Haskell Weird Kinds: Kind of (->) is ?? -> ? -> *
I think a function can't return an unboxed type because functions are lazy. Since a lazy value is either a value or a thunk, it has to be boxed. Boxed just means it is a pointer rather than just a value: it's like Integer()
vs int
in Java.
Since you are probably not going to be using unboxed types in LYAH-level code, you can imagine that the kind of ->
is just * -> * -> *
.
Since the ?
and ??
are basically just more general version of *
, they do not have anything to do with sorts or anything like that.
However, since ->
is just a type constructor, you can actually partially apply it; for example, (->) e
is an instance of Functor
and Monad
. Figuring out how to write these instances is a good mind-stretching exercise.
As far as value constructors go, they would have to just be lambdas (\ x ->
) or function declarations. Since functions are so fundamental to the language, they get their own syntax.