Haskell from the ground up

Your own types, and what deriving gives you

data builds a type of your own out of named fields, deriving asks for the abilities the type should have, and a lowercase name in a signature means any type at all.

Part 10 of 12

Advanced20 min readBack to Courses

Give your data a name

data introduces a type of your own. data Pet = Pet String Int says: there is a type called Pet, and the only way to make one is the constructor Pet, which takes a name and an age. The constructor is an ordinary function from those fields to the value.

A function takes your type apart the same way it takes a list apart: write the constructor in the pattern and name the fields. name (Pet n a) = n matches any value built with Pet and hands you its two halves.

A new type knows nothing yet

A fresh type cannot be printed, compared or ordered, because nothing has said what those words mean for it. deriving (Show, Eq, Ord) asks the compiler to fill them in: Show gives print and show, Eq gives ==, and Ord gives <, compare and maximum.

A group of abilities like this is called a typeclass. Num is the one that numbers have, which is why + works on Int and on Double. When a signature says the type must have an ability, it writes that ability as a constraint before an arrow that looks like =>.

A shape for every type

A lowercase name in a type is not a type, it is a place where any type can stand. count :: [a] -> Int works on a list of anything at all, because it only ever counts how many values are there; it never looks at one.

Add a constraint and the function is narrowed to types with that ability: with a constraint saying the type can be ordered, the function may put the values into order. The a is chosen by the caller, and the compiler checks that the caller choice has every ability the function asked for.

A synonym is the lightest way to name a type: type Name = String says that Name and String are the same type under two spellings. It costs nothing at run time and it makes a signature say what a value is for. type Price = Int makes a signature say which number is which, even though the compiler treats both spellings as Int.

A synonym does not create a new type, so the compiler still accepts a Price where a Quantity was expected. When that confusion has to be impossible, use data or newtype, which do create a type of their own. The choice is between a name for the reader and a wall for the compiler.

A type of your own

data Point = Point Int Int
  deriving (Show, Eq)

moveRight :: Point -> Point
moveRight (Point x y) = Point (x + 1) y

count :: [a] -> Int
count = length

Two pets and their abilities

Run it and read the five lines. Then give the type a third field, a team or a colour, and decide whether the comparison line still says what you expect.

Output
Click "Run" to execute your code

The order of the fields decides the order that deriving (Ord) gives your type, which is handy when the first field is the amount or the date you want to sort by. If the order matters to the program, design the type with the field you compare first.

A value is one constructor or the other

When a type has several constructors, a value of that type is exactly one of them. Circle 2 is a Shape, Rect 3 4 is a Shape, and no value is both. A function over Shape therefore writes one equation per constructor, and the compiler checks that each one is reachable.

Adding a constructor stays safe: add Triangle and every function that matches on Shape needs another equation; with warnings on, the compiler lists the incomplete ones.

The constructor is also an ordinary function: Circle has the type Double -> Shape, so it can be passed to map like any other function.

What deriving actually does

deriving (Show) writes a show that prints the constructor and its fields, which is why a derived value looks like the code that built it. It is useful for debugging and rarely the format a report wants, which is why a program printed for a reader writes its own line function.

deriving (Eq) compares the constructors first, then the fields. Circle 2 == Circle 2 is True, and Circle 2 == Rect 2 2 is False without looking at the numbers, because the constructors differ.

deriving (Ord) uses the order the constructors are declared in before it compares any field, and only then compares the fields left to right. Reordering the constructors in the data line changes the sort order of every value, even though no equation changed.

A function that forgets a constructor still compiles and stops at run time when a value built with that constructor arrives, so turn warnings on and treat that one as an error. Two fields of the same type can be swapped without a complaint: Rect 3 4 and Rect 4 3 both type-check, and only your own naming can tell width from height.

Try these next

  • Add a Triangle Double Double constructor and let the compiler list every function that needs a new equation.
  • Write a perimeter function for both shapes and print it beside the area.
  • Change the order of the constructors and predict which of two shapes now compares smaller.

Two shapes, one type

Run it and read the five lines. Then add a `Triangle Double Double` constructor with an area of half the base times the height, and follow the warnings until every function handles it.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does data Pet = Pet String Int define?