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.
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 = lengthTwo 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.
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 Doubleconstructor and let the compiler list every function that needs a new equation. - Write a
perimeterfunction 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.
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?
