In Haskell, types are how you describe the data your program will work with.
One introduces, or declares, at type in Haskell via the
data statement. In general a data declaration looks like:
data [context =>] type tv1 ... tvi = con1 c1t1 c1c2... c1tn | ... | conm cmt1 ... cmtq [deriving]
which probably explains nothing if you don't already know Haskell!
The essence of the above statement is that you use the keyword
supply an optional context, give the type name and a variable number of
type variables. This is then followed by a variable number of constructors, each of which has a list of type variables or type constants. At the end, there is an optional
There are a number of other subtelties associated with this, such as requiring parameters to the data constructors to be eager, what classes are allowed in the deriving, use of field names in the constructors and what the context actually does. Please refer to the specific articles for more on each of those.
Let's look at some examples. The Haskell standard data type Maybe is typically declared as:
data Maybe a = Just a | Nothing
What this means is that the type Maybe has one type variable, represented by the a and two constructors Just and Nothing. (Note that Haskell requires type names and constructor names to begin with an uppercase letter). The Just constructor takes one parameter, a.
As another example, consider binary Trees. They could be represented by:
data Tree a = Branch (Tree a) (Tree a) | Leaf a
Here, one of the constructors, Branch of Tree takes two trees as parameters to the constructor, while Leaf takes the type variable a. This type of recursion is a very common pattern in Haskell.
Type and newtype
The other two ways one may introduce types to Haskell programs are via the
type introduces a synonym for a type and uses the same data
newtype introduces a renaming of a type and
requires you to provide new constructors.
When using a
type declaration, the type synonym and its base type
are interchangeble almost everywhere (There are some restrictions when dealing with instance declarations). For example, if you had the declaration:
type Name = String
then any function you had declared that had
String in its
signature could be used on any element of type
However, if one had the declaration:
newtype FirstName = FirstName String
this would no longer be the case. Functions would have to be declared that actually were defined on FirstName. Often, one creates a deconstructor at the same time which helps alleviate this requirement. e.g.:
unFirstName :: FirstName -> String unFirstName (FirstName s) = s
Further illustrative examples would be most appreciated.