In Haskell, (value-level) expressions are classified into types, which can be notated with ::
like so: 3 :: Int
, "Hello" :: String
, (+ 1) :: Num a => a -> a
. Similarly, types are classified into kinds. In GHCi, you can inspect the kind of a type expression using the command :kind
or :k
:
> :k Int
Int :: *
> :k Maybe
Maybe :: * -> *
> :k Either
Either :: * -> * -> *
> :k Num
Num :: * -> Constraint
> :k Monad
Monad :: (* -> *) -> Constraint
There are definitions floating around that *
is the kind of "concrete types" or "values" or "runtime values." See, for example, Learn You A Haskell. How true is that? We've had a few questions about kinds that address the topic in passing, but it'd be nice to have a canonical and precise explanation of *
.
What exactly does *
mean? And how does it relate to other more complex kinds?
Also, do the DataKinds
or PolyKinds
extensions change the answer?
First off,
*
is not a wildcard! It's also typically pronounced "star."Bleeding edge note: There is as of Feb. 2015 a proposal to simplify GHC's subkind system (in 7.12 or later). That page contains a good discussion of the GHC 7.8/7.10 story. Looking forward, GHC may drop the distinction between types and kinds, with
* :: *
. See Weirich, Hsu, and Eisenberg, System FC with Explicit Kind Equality.The Standard: A description of type expressions.
The Haskell 98 report defines
*
in this context as:In this context, "nullary" simply means that the constructor takes no parameters.
Either
is binary; it can be applied to two parameters:Either a b
.Maybe
is unary; it can be applied to one parameter:Maybe a
.Int
is nullary; it can be applied to no parameters.This definition is a little bit incomplete on its own. An expression containing a fully-applied unary, binary, etc. type constructor also has kind
*
, e.g.Maybe Int :: *
.In GHC: Something that contains values?
If we poke around the GHC documentation, we get something closer to the "can contain a runtime value" definition. The GHC Commentary page "Kinds" states that "'
*
' is the kind of boxed values. Things likeInt
andMaybe Float
have kind*
." The GHC user's guide for version 7.4.1, on the other hand, stated that*
is the kind of "lifted types". (That passage wasn't retained when the section was revised forPolyKinds
.)Boxed values and lifted types are a bit different. According to the GHC Commentary page "TypeType",
So
ByteArray#
, the type of raw blocks of memory, is boxed because it is represented as a pointer, but unlifted because bottom is not an element.Therefore it appears that the old User's Guide definition is more accurate than the GHC Commentary one:
*
is the kind of lifted types. (And, conversely,#
is the kind of unlifted types.)Note that if types of kind
*
are always lifted, for any typet :: *
you can construct a "value" of sorts withundefined :: t
or some other mechanism to create bottom. Therefore even "logically uninhabited" types likeVoid
can have a value, i.e. bottom.So it seems that, yes,
*
represents the kind of types that can contain runtime values, ifundefined
is your idea of a runtime value. (Which isn't a totally crazy idea, I don't think.)GHC Extensions?
There are several extensions which liven up the kind system a bit. Some of these are mundane:
KindSignatures
lets us write kind annotations, like type annotations.ConstraintKinds
adds the kindConstraint
, which is, roughly, the kind of the left-hand side of=>
.DataKinds
lets us introduce new kinds besides*
and#
, just as we can introduce new types withdata
,newtype
, andtype
.With
DataKinds
everydata
declaration (terms and conditions may apply) generates a promoted kind declaration. Sointroduces the usual value constructor and type name; additionally, it produces a new kind,
Bool
, and two types:True :: Bool
andFalse :: Bool
.PolyKinds
introduces kind variables. This just a way to say "for any kindk
" just like we say "for any typet
" at the type level. As regards our friend*
and whether it still means "types with values", I suppose you could say a typet :: k
wherek
is a kind variable could contain values, ifk ~ *
ork ~ #
.For beginners that are trying to learn about kinds (you can think of them as the type of a type) I recommend this chapter of the Learn you a Haskell book.
I personally think of kinds in this way:
You have concrete types, e.g.
Int
,Bool
,String
,[Int]
,Maybe Int
orEither Int String
.All of these have the kind
*
. Why? Because they can't take any more types as a parameter; anInt
, is anInt
; aMaybe Int
is aMaybe Int
. What aboutMaybe
or[]
orEither
, though?When you say
Maybe
, you do not have a concrete type, because you didn't specify its parameter.Maybe Int
orMaybe String
are different but both have a*
kind, butMaybe
is waiting for a type of kind*
to return a kind*
. To clarify, let's look at what GHCI's:kind
command can tell us:With lists it's the same:
What about
Either
?You could think of intuitively think of
Either
as a function that takes parameters, but the parameters are types:means
Either Int
is waiting for a type parameter.In the most basic form of the kind language, where there are only the kind
*
and the kind constructor->
, then*
is the kind of things that can stand in a type-of relationship to values; nothing with a different kind can be a type of values.Types exist to classify values. All values with the same type are interchangeable for the purpose of type-checking, so the type checker only has to care about types, not specific values. So we have the "value level" where all the actual values live, and the "type level" where their types live. The "type-of" relationship forms links between the two levels, with a single type being the type of (usually) many values. Haskell makes these two levels quite explicit; it's why you can have declarations like
data Foo = Foo Int Chat Bool
where you've declared a type-level thingFoo
(a type with kind*
) and a value-level thingFoo
(a constructor with typeInt -> Char -> Bool -> Foo
). The twoFoo
s involved simply refer to different entities on different levels, and Haskell separates these so completely that it can always tell what level you're referring to and thus can allow (sometimes confusingly) things on the different levels to have the same name.But as soon as we introduce types that themselves have structure (like
Maybe Int
, which is a type constructorMaybe
applied to a typeInt
), then we have things that exist at the type level which do not actually stand in a type-of relationship to any values. There are no values whose type is justMaybe
, only values with typeMaybe Int
(andMaybe Bool
,Maybe ()
, evenMaybe Void
, etc). So we need to classify our type-level things for the same reason we need to classify our values; only certain type-expressions actually represent something that can be the type of values, but many of them work interchangeably for the purpose of "kind-checking" (whether it's a correct type for the value-level thing it's declared to be the type of is a problem for a different level).1So
*
(which is often stated to be pronounced "type") is the basic kind; it's the kind of all type-level things that can be stated to be the type of values.Int
has values; therefore its type is*
.Maybe
does not have values, but it takes an argument and produces a type that has values; this gets us a kind like___ -> *
. We can fill in the blank by observing thatMaybe
's argument is used as the type of the value appearing inJust a
, so its argument must also be a type of values (with kind*
), and soMaybe
must have kind* -> *
. And so on.When you're dealing with kinds that only involve stars and arrows, then only type-expressions of kind
*
are types of values. Any other kind (e.g.* -> (* -> * -> *) -> (* -> *)
) only contains other "type-level entities" that are not actual types that contain values.PolyKinds
, as I understand it, doesn't really change this picture at all. It just allows you to make polymorphic declarations at the kind-level, meaning it adds variables to our kind language (in addition to stars and arrows). So now I can contemplate type-level things of kindk -> *
; this could be instantiated to work as either kind* -> *
or(* -> *) -> *
or(* -> (* -> *)) -> *
. We've gained exactly the same kind of power as having(a -> b) -> [a] -> [b]
at the type level gained us; we can write onemap
function with a type that contains variables, instead of having to write every possible map function separately. But there's still only one kind that contains type-level things that are the types of values:*
.DataKinds
also introduces new things to the kind language. Effectively what it does though is to let us declare arbitrary new kinds, which contain new type-level entities (just as ordinarydata
declarations allow us to declare arbitrary new types, which contain new value-level entities). But it doesn't let us declare things with a correspondence of entities across all 3 levels; if I havedata Nat :: Z | S Nat
and useDataKinds
to lift it to the kind level, then we have two different things namedNat
that exist on the type level (as the type of value-levelZ
,S Z
,S (S Z)
, etc), and at the kind level (as the kind of type-levelZ
,S Z
,S (S Z)
). The type-levelZ
is not the type of any values though; the valueZ
inhabits the type-levelNat
(which in turn is of kind*
), not the type-levelZ
. SoDataKinds
adds new user defined things to the kind language, which can be the kind of new user-defined things at the type level, but it remains the case that the only type-level things that can be the types of values are of kind*
.The only addition to the kind language that I'm aware of which truly does change this are the kinds mentioned in @ChristianConkle's answer, such as
#
(I believe there are a couple more now too? I'm not really terribly knowledgeable about "low level" types such asByteArray#
). These are the kinds of types that have values that GHC needs to know to treat differently (such as not assuming they can be boxed and lazily evaluated), even when polymorphic functions are involved, so we can't just attach the knowledge that they need to be treated differently to these values' types, or it would be lost when calling polymorphic functions on them.1 The word "type" can thus be a little confusing. Sometimes it is used to refer to things that actually stand in a type-of relationship to things on the value level (this is the interpretation used when people say "
Maybe
is not a type, it's a type-constructor"). And sometimes it's used to refer to anything that exists at the type-level (under this interpretationMaybe
is in fact a type). In this post I'm trying to very explicitly refer to "type-level things" rather than use "type" as a short-hand.