Type system
Type declarations
double :: Int -> Int is the type declaration for the double
function. It says it takes one argument of type Int and returns a
value of type Int. Types do not always have to be explicitly
specified. Haskell has type inference which mean the compiler can work
out the types itself.
Common built in types
-
Char- A single character -
String- A list of characters -
Int- A bounded integer, usually either 32 or 64 bits -
Integer- An arbitrary precision integer which can hold as large of a number as the system has memory for -
Bool- A Boolean value -
Float- A single precision floating point number -
Double- A double precision floating point number
Polymorphism
Consider the identity function, id x = x. This function can take an
argument of any type and return a value of the same type. It has the
polymorphic type signature id :: a -> a. Polymorphic types can be any
type.
Data declarations
The data keyword can be used to define new data types.
data Bool = True | False
Bool is the name of the data type and True and False are its
constructors. True and False are both of type Bool.
Polymorphic data types
Similarly to polymorphic functions, it is possible to have polymorphic data types.
data Maybe a = Nothing | Just a
The Maybe data type has two constructors - Nothing :: Maybe a and
Just :: a -> Maybe a. a is a polymorphic variable and means Maybe
can wrap around any type. These can be constructed as follows:
noNumber :: Maybe Int
noNumber = Nothing
yesBool :: Maybe Bool
yesBool = Just True
These values can be pattern matched in functions. For example a function
valueOrZero can take a Maybe Int and return an Int.
valueOrZero :: Maybe Int -> Int
valueOrZero (Just x) = x
valueOrZero Nothing = 0
Sum and product types and cardinality
Sum types are of the form data Type = Con1 | Con2 and product types
are of the form data Type = Type Int Int. The cardinality of a type is
the number of possible values it can take. For example the type Bool
can either be true or false so it has cardinality 2. A type
data TwoBools = TwoBools Bool Bool is a product type and has
cardinality 4, the product of 2 and 2.
data SomeBools = NoBools | TwoBools Bool Bool will have cardinality 5,
the sum of the cardinalities of its constructors.
Newtype and type
The newtype keyword is used to create a type with a single
constructor. For example newtype Age = Age Int. Age and Int can't
be used interchangeably.
type can be used to define another name for a type. It is a synonym,
not a separate type. For example type String = [Char]
Recursive data types
A recursive data type refers to itself in its definition. Consider the definition of a list-like type:
data List a = Empty | Cons a (List a)
This is a recursive data type as the definition of list refers to itself.
Records
Record syntax is a convenient way to create large product types. Consider a type which represents a person:
data Person = Person String String (Maybe String) Int
bob :: Person
bob = Person "Robert" "Smith" (Just "Bob") 63
The meaning of the arguments to Person are not immediately obvious and
this is prone to mistakes. It is also difficult to extract data from the
type.
Instead, the Person type can be defined using record syntax.
data Person = Person {
firstName :: String,
lastName :: String,
preferredName :: Maybe String,
age :: Int
}
bob = Person{
firstName = "Robert",
lastName = "Smith",
preferredName = Just "Bob",
age = 63
}
This is much more readable and less prone to error.
Records also define accessor functions for each field. These accessor
functions have the same name as the fields and given a record value it
will return the value for the corresponding field. For example,
firstName bob would return Robert. It is important that field names
are chosen such that they don't cause naming collisions.
It is also easier to update values in record syntax. For example, a function which updates the first name would be written as follows:
updateFirstName newName person = person {firstName = newName}
Kinds
The types of types and type constructors are called kinds. Types which
take no parameters are of kind * such as String :: *. Types like
Maybe which take one type parameter are of type * -> *. When a type
parameter is applied the kind is reduced, so Maybe :: * -> * whereas
Maybe Int :: *. All fully applied data types and all primitive types
are of kind * . Kinds allow the compiler to determine which types are
well formed. Kinds also determine which constructors can be instantiated
for a type class. Show can only be defined for fully qualified types
(kind *) and Foldable can only be defined for constructorss with a
single type parameter, kind *->*.