Skip to main content

Defining data types

New datatypes with the data construct​

data Weekday = Mo | Tu | We | Th | Fr | Sa | Su

This is an enumeration type. There are 7 constructors:

Mo, Tu, We, Th, Fr, Sa, Su :: Weekday
data Date = D Int Int Int -- year, month, day

This is a record type. It has a single constructor:

D :: Int -> Int -> Int -> Date

(Data) Constructors​

Mo :: Weekday
D :: Int -> Int -> Int -> Date
False :: Bool
(:) :: a -> [a] -> [a]
(,) :: a -> b -> (a, b)
Just :: a -> Maybe a
  • Constructors are constants or functions that can be used to construct terms on the right hand side of a declaration.
  • They have types targetting the datatype they belong to.
  • Constructors determine the shape of values – they are not reduced, but evaluate to themselves.
  • We can pattern-match on constructors (and not on ordinary constants or functions).

Datatypes yield programming patterns​

From the datatype definition, we can read off the standard design principle for functions over the datatype:

  • For each constructor, make a case.
  • Use the arguments of the constructor on the right hand side.
  • Whenever the datatype is recursive, consider making the function recursive.

Booleans​

data Bool = False | True

An enumeration type, like Weekday.

Two constructors, no recursion.

Example functions:

not :: Bool -> Bool
not False = True
not True = False
(&&) :: Bool -> Bool -> Bool
(&&) True True = True
(&&) __ = False

Tuples​

data (a, b) = (a, b)
data (a, b, c) = (a, b, c)

Parameterized. One constructor each. No recursion. Built-in syntax.

We could define our own, with less convenient syntax:

data Pair a b = MakePair a b
data Triple a b c = MakeTriple a b c

Example functions:

secondOfThree :: (a, b, c) -> b
secondOfThree (x, y, z) = y
secondOfThree' :: Triple a b c -> b
secondOfThree' (MakeTriple x y z) = y

Maybe​

data Maybe a = Nothing | Just a

Parameterized. Two constructors. No recursion. Example function:

fromMaybe :: a -> Maybe a -> a
fromMaybe def Nothing = def
fromMaybe def (Just x) = x

Lists​

data [a] = [] | a : [a]

Parameterized. Two constructors. Recursive. Built-in syntax. We could define our own, with less convenient syntax.

data List a = Nil | Cons a (List a)

We have seen lots of example functions following the standard design principle.

The data construct​

The syntax of the data construct:

data Type arg1...argm = Con1 ty1 ... tyn
| Con2 ...
| ...

Introduces the new datatype Type and the data constructors Con1 , Con2 , ... .

Types of constructors are determined by the data declaration:

Con1 :: ty1 -> ... -> tyn -> Type arg1 ... argm

Type and constructor names must start with an uppercase letter; symbolic infix constructors must start with a colon ( : ). Lists and tuples support additional built-in syntax that cannot be used for other datatypes.

Applying the design principle​

Also for new datatypes, always keep in mind that by looking at the datatype, you obtain a design principle for functions over that type:

data Weekday = Mo | Tu | We | Th | Fr | Sa | Su

```hs
isWeekend :: Weekday -> Bool
isWeekend Mo = False
isWeekend Tu = False
isWeekend We = False
isWeekend Th = False
isWeekend Fr = False
isWeekend Sa = True
isWeekend Su = True
isWeekend :: Weekday -> Bool
isWeekend Sa = True
isWeekend Su = True
isWeekend _ = False

Collapsing cases – the order of cases then matters!

Another example​

data Date = D Int Int Int -- year, month, day

One constructor. No recursion.

Example function:

valid :: Date -> Bool
valid (D y m d) =
m >= 1 && m <= 12 && d >= 1 && d <= 31

Of course, this is not an optimal definition.

Type synonyms with type​

Often, for datatypes with a single constructor, the constructor is named the same as the datatype itself:

data Date = Date Int Int Int

It’s often better to give more meaningful names to types without creating a completely new type:

type Year = Int
type Month = Int
type Day = Int
data Date = Date Year Month Day

Note that type introduces type synonyms. For example, 2 :: Year and 2 :: Int. No conversion function is required.

Renamed types with newtype​

We could also define:

data Year = Year Int

Now Year and Int are different:

Year :: Int -> Year -- the constructor

To extract the Int from a year, we can use pattern matching:

fromYear :: Year -> Int
fromYear (Year n) = n

For the case of a single-constructor, single-argument datatype (i.e., a renamed type), there’s a more efficient construct:

newtype Year = Year Int

Lessons​

  • Let the types guide you.
  • Use pattern matching to get at components of values, and to distinguish cases.
  • Try to follow the recursive structure of types (lists, trees).
  • Cover all cases.
  • Use precise types, such as Maybe , rather than causing uncontrolled errors.