Explaining Kinds
This code...
function :: Int -> String
function x = "an integer value!"
... translates to, "I cannot give you a concrete value (i.e. String
) until you give me an Int
value."
Similarly, this code...
data Box a = Box a
... translates to, "I cannot give you a concrete type (e.g. Box Int
, a box that stores an Int
value (rather than a String
value or some other value)) until you tell me what a
is."
Let's rewrite the above Box
type. Things on the left of the =
indicate type information. Things on the right of the =
indicate value information.
{-
| Type information | Value information | -}
data BoxType a = BoxValue a
The above code now says, "I cannot give you a concrete type (e.g. BoxType Int
) until you tell me what a
is." Let's assume that a
is Int
. We would say that BoxValue 4
is a value whose type is BoxType Int
.
What are Kinds and Kind Signatures?
Kinds = "How many more types do I need defined before I have a 'concrete' type?"^^
^^ This is a "working definition." There's more to it than that when one considers type-level programming, but for now, this will suffice."
We saw earlier that we annotate functions with type signatures via ->
:
-- ||
-- \/
function :: Int -> String
function x = "an integer value!"
The ->
indicates that the thing to the right (i.e. String
) cannot be produced until it is given the thing to the left of it (i.e. Int
).
Type signatures annotate value-level entities like values (i.e. 4
or BoxValue
) and functions.
Kind signatures annotate type-level entities like BoxType
. They are basically type signatures for types, not values.
# of types that still need to be defined | Special Name | Their "kind signature" (Purescript)^^ | Their "kind signature" (Haskell)^^ |
---|---|---|---|
0 | Concrete Type | Type | * |
1 | Higher-Kinded Type (by 1) | Type -> Type | * -> * |
2 | Higher-Kinded Type (by 2) | Type -> Type -> Type | * -> * -> * |
n | Higher-Kinded Type (by n) | ... Type ... -> Type | ... * ... -> * |
^^ These columns are right-aligned to show that the right-most Type
/*
is the "concrete" type. Also, the ... Type ... -> Type
(and its Haskell equivalent) syntax is not real syntax but merely conveys the recursive idea in an n-kinded type. The other three (0 - 2) are real syntax.
Concrete Types
Concrete types can usually be written with literal values:
integerValue :: Int
integerValue = 1
(1 :: Int) -- this is notation for saying that `1` is a value of type, `Int`.
stringValue :: String
stringValue = "a literal string"
("a literal string" :: String)
data BoxType a = BoxValue a
boxWithOneIntValue :: BoxType Int
boxWithOneIntValue = BoxValue 4
((BoxValue 4) :: BoxType Int)
arrayOfIntsValue :: Array Int
arrayOfIntsValue = [1, 2, 3]
([1, 2, 3] :: Array Int)
Higher-Kinded Types
Higher-kinded types are those that still need one or more types to be defined.
-- Kind Signature: Type -> Type
-- Reason: the `a` type needs to be defined
data Box a = Box a
-- This is the same definition as above.
-- However, the kind signature of the above `Box` definition is implicit.
-- The below definition has an explicit kind signature.
data BoxType :: Type -> Type
data BoxType a = BoxValue a
-- As we can see, there can be many different concrete 'Box' types
-- depending on what 'a' is:
boxedInt :: Box Int
boxedInt = Box 4
boxedString :: Box String
boxedString = Box "string"
boxedBoxedInt :: Box (Box Int)
boxedBoxedInt = Box boxedInt
We can make the type's kind higher by adding more types that need to be specified. For example:
-- A box that holds two values of same or different types!
-- Kind Signature: `Type -> Type -> Type`
data BoxOfTwo a b = BoxOfTwo a b
data BoxOfTwo_ExplicitKindSignature :: Type -> Type -> Type
data BoxOfTwo_ExplicitKindSignature a b = BoxOfTwoValue a b
-- The below syntax is not valid because it is missing `forall a b.`,
-- but it gets the idea across. The "forall" syntax will be covered later.
higherKindedBy2 :: a -> b -> BoxOfTwo a b
higherKindedBy2 a b = BoxOfTwo a b
-- We can lower the kind by specifying one of the data types:
higherKindedBy1L :: b -> BoxOfTwo Int b
higherKindedBy1L b = BoxOfTwo 3 b
higherKindedBy1R :: a -> BoxOfTwo a String
higherKindedBy1R a = BoxOfTwo a "a string value"
concreteType :: BoxOfTwo Int String
concreteType = BoxOfTwo 3 "a string value"
Generic types can also be split across the values of a type:
-- It's either an A or it's a B, but not both!
-- Kind signature is implicit: `Type -> Type -> Type`
data Either a b
= Left a
| Right b
data Either_ExplicitKindSignature :: Type -> Type -> Type
data Either_ExplicitKindSignature a b
= Left a
| Right b
higherKindedBy2L :: a -> b -> Either a b
higherKindedBy2L a b = Left a
higherKindedBy2R :: a -> b -> Either a b
higherKindedBy2R a b = Right b
higherKindedBy1L_ignoreB :: b -> Either Int b
higherKindedBy1L_ignoreB b = Left 3
higherKindedBy1L_useB :: b -> Either Int b
higherKindedBy1L_useB b = Right b
higherKindedBy1L_ignoreBoth :: a -> b -> Either Int b
higherKindedBy1L_ignoreBoth a b = Left 3
Either
(where the a
and b
are not yet specified) has kind Type -> Type -> Type
because it cannot become a concrete type until both a
and b
types are defined, even if only constructing one of its values whose generic type is known.
In other words
allSpecified :: Either Int String
allSpecified = Right "foo"
{-
(value) -}
(Right "foo") {-
(value :: Type ) -}
(Right "foo" :: Either Int String) {-
((value :: Type ) :: Kind) -}
((Right "foo" :: Either Int String) :: Type) {-
((value :: Type ) :: Kind ) -}
((Right "foo" :: Either a String) :: Type -> Type)
Table of Inferred Types
Inferred kind | |
---|---|
Unit | Type |
Array Boolean | Type |
Array | Type -> Type |
Either Int String | Type |
Either Int b | Type -> Type |
Either a String | Type -> Type |
Either | Type -> Type -> Type |
... | ... |