← Fundamentals
Guided lesson 7 questions · Haskell only

Haskell type classes, data, and newtype

type gives a second name, newtype makes a separate type, and data declares fields and constructors. Each new type gets only the instances you give it.

type gives an existing type a second name and changes nothing. newtype makes a separate type with one constructor and one field, which GHC keeps apart from every other type and which costs nothing when the program runs. data declares several fields or constructors. A new type starts with no instances: deriving writes Show, Eq, and Ord for you, and an instance you write yourself decides what show and compare mean.

Written for Haskell.

Before you begin

Why this matters

Distances, times, and ids are often plain Ints, and a swapped argument then gives a wrong number instead of an error. If you know which declaration makes a separate type, and what a derived instance does, you can make GHC catch the swap and predict how your values sort and print.

What this helps you reason about

  • Predict whether GHC accepts a distance passed where a time was expected.
  • Predict the order that a derived Ord gives, from the declaration alone.
  • Write a Show or Ord instance yourself, and spot a Num instance that lets wrong arithmetic through.

Many languages let you give an existing type a second name, and some let you make a new type from an old one. Haskell has both, with two different keywords, type and newtype, and a third keyword, data, for types with several fields or constructors. Interfaces, traits, and type classes showed that a type joins a class only through an instance, and that a constraint such as Vehicle a => asks for one. This lesson compares type, newtype, and data, and shows where the instances of a new type come from: deriving, or code you write.

Already know this? The short version
  • type Metres = Int is a second name for Int, so a Metres and a Seconds mix freely; newtype Metres = Metres Int is a separate type, and a swap gives Couldn’t match expected type ‘Seconds’ with actual type ‘Metres’.
  • A newtype has exactly one constructor with one field and costs nothing at run time; data is for several fields or constructors.
  • A new type has no instances; deriving (Show, Eq, Ord) writes them, and a derived compare looks at constructor order first, then at the fields from left to right.
  • A hand-written instance decides show or compare; an Ord instance defines compare and must agree with ==, and a Num instance for a unit also accepts * and bare number literals.
  • The section “What goes wrong with types and instances in real programs?” covers synonyms that let units mix, a derived Ord on the wrong field, orphan instances, and show used for saving with read for loading.

Go to the final check· Go to the practice tickets

Words in this lesson (8)
type synonym
A second name for an existing type, declared with type, such as type Metres = Int. To GHC, Metres and Int are the same type.
newtype
A declaration such as newtype Metres = Metres Int that makes a separate type with one constructor and one field. GHC keeps it apart from the wrapped type, and it costs nothing when the program runs.
data constructor
The name that builds a value of a data type, such as Employee in Employee "Noor" "Design" 30. It takes the fields in the order they are declared and runs no other code.
instance
The code that makes one type a member of a type class, written instance Eq Jar where … or produced with deriving.
deriving
A clause, such as deriving Eq after a data declaration, that asks GHC to write an instance for you. A derived Eq compares the constructor and every field.
Ord
The type class for ordering, with compare, < and >. Data.Map and Data.Set find keys with compare, so Ord must agree with Eq.
Num
The type class for numbers, with +, -, *, negate, abs, signum, and fromInteger. fromInteger turns a number literal such as 5 into a value of the type.
orphan instance
An instance written in a module that declares neither the class nor the type. Two modules can then give one type two instances of one class.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / type

What does a type synonym give a name to?

type Metres = Int declares a type synonym: a second name for Int. It makes no new type. To GHC, Metres and Seconds below are both Int, so either one fits wherever the other is expected.

That is the trap. pace takes the time first and the distance second. The application pace 5000 1500 has them the other way round, and GHC accepts it. The program runs and gives a wrong number, 3333 instead of 300.

A type synonym is still useful. It tells a reader what a number means, and it shortens a long type, such as type Board = [(String, Int)]. It never stops a mix-up.

Main.hs
type Metres = Int
type Seconds = Int

-- seconds for each kilometre
pace :: Seconds -> Metres -> Int
pace time distance = time * 1000 `div` distance

-- pace 1500 5000 is 300
-- pace 5000 1500 is 3333, and GHC accepts it

A type synonym is another name for the same type; two synonyms of Int mix freely.

Coming from Go: Go’s type Metres int makes a new type that needs a conversion. Haskell’s type does not; Haskell’s newtype does.

Coming from TypeScript: Haskell’s type works like TypeScript’s type Metres = number: a second name that the checker treats as the same type.

Experiment

Pass a distance where a time was expected

Metres and Seconds are both names for Int, so pace’s type is Int -> Int -> Int. Any two Ints fit, in either order. GHC has nothing to check.

Why it matters A type synonym documents a value but never protects it. When two values must not be swapped, give them different types.

pace takes the time first, but main passes the distance first. Predict whether GHC accepts the program, and what it prints. Then change the last line to print (pace time distance) and predict again.

type Metres = Int
type Seconds = Int

-- seconds for each kilometre
pace :: Seconds -> Metres -> Int
pace time distance = time * 1000 `div` distance

main :: IO ()
main = do
  let distance = 5000 :: Metres
      time = 1500 :: Seconds
  print (pace distance time)
What to look for

Each stop shows one place, such as Main.pace, not a list of frames, and _ (unevaluated) marks a value that has not been computed yet. The stops are line 9, line 12 twice, and line 6 (Main.pace) twice, where time and distance are both unevaluated. pace computes time * 1000 first, so it needs its parameter time. The next stop is line 10, Main.main.distance: the value pace calls time is main’s distance. Line 11 comes last, and the run prints 3333. After the edit, line 11 (time) comes before line 10, and the run prints 300.

Expected output

3333

02 / newtype

How does newtype make a separate type?

newtype Metres = Metres Int declares a separate type, Metres, with one data constructor, also called Metres, that wraps one Int. The type and the constructor may share a name. The type appears in signatures, and the constructor in expressions and patterns. Metres 5000 builds a value. The pattern (Metres distance) takes the value apart and names the Int inside.

Now Metres, Seconds, and Int are three different types. With the arguments swapped, GHC reports Couldn’t match expected type ‘Seconds’ with actual type ‘Metres’, and the same error the other way round for the second argument.

A newtype costs nothing when the program runs: GHC gives it the same representation as the Int inside, so wrapping and unwrapping do no work. In exchange, a newtype has exactly one constructor with exactly one field. For anything else, use data: several fields, as in data Run = Run Metres Seconds, or several constructors, as in Alternatives and combinations.

Main.hs
newtype Metres = Metres Int
newtype Seconds = Seconds Int

pace :: Seconds -> Metres -> Int
pace (Seconds time) (Metres distance) =
  time * 1000 `div` distance

-- pace (Seconds 1500) (Metres 5000) is 300
-- pace (Metres 5000) (Seconds 1500) does not compile

A newtype is a separate type with one constructor and one field; GHC rejects a swap, and the wrapper costs nothing at run time.

Coming from Rust: struct Metres(u32) is Rust’s newtype: a separate type with no cost at run time. Haskell has a keyword for it.

Precisely: newtype and data with one field

data Metres = Metres Int also makes a separate type, but it is not the same as the newtype. A data value is a box of its own: GHC usually stores the constructor and a pointer to the Int. And matching the pattern Metres d on a data value computes the value, while on a newtype the match does nothing, because there is no box to open. The Haskell 2010 Report, section 4.2.3, describes this difference.

Experiment

Swap two newtypes

Metres and Seconds are now two types, each different from Int and from each other. pace’s first parameter accepts a Seconds value and nothing else, so the swap is a type error. The patterns (Seconds time) and (Metres distance) take the Ints out again.

Why it matters Give each unit its own newtype, and GHC finds a swapped argument before the program runs.

This is the same program with newtypes. Predict what it prints. Then swap the arguments on the last line to print (pace distance time), and predict what GHC says.

newtype Metres = Metres Int
newtype Seconds = Seconds Int

-- seconds for each kilometre
pace :: Seconds -> Metres -> Int
pace (Seconds time) (Metres distance) = time * 1000 `div` distance

main :: IO ()
main = do
  let distance = Metres 5000
      time = Seconds 1500
  print (pace time distance)
What to look for

The stops are the same as in the first experiment: line 9, line 12 twice, line 6 (Main.pace) twice, then line 11 (time) and line 10 (distance). The run prints 300. The stepper never shows a Metres value, because each one is still unevaluated when the stepper passes it. After the edit, the program does not compile. GHC reports two errors on line 12. The first points at distance: Couldn’t match expected type ‘Seconds’ with actual type ‘Metres’. The second points at time: Couldn’t match expected type ‘Metres’ with actual type ‘Seconds’.

Expected output

300

03 / deriving

What do derived Show, Eq, and Ord compare and write?

A new type, from newtype or data, starts with no instances: no show, no ==, no <. Equality and comparison derived Eq. deriving (Show, Eq, Ord) asks GHC to write three instances at once.

A derived show writes the value as Haskell code: Finished 1620, or Runner {name = "Ivy", time = 1620} for a record. A derived == compares the constructor and every field.

A derived compare first compares the constructors, in the order the data declaration lists them: a constructor listed earlier is smaller. Only when both values have the same constructor does it compare the fields, from left to right. Below, Finished is listed before Retired, so every Finished value is smaller than Retired. minimum gives the smallest element of a list, so it finds the fastest finisher and never a runner who retired.

Main.hs
data Finish = Finished Int | Retired
  deriving (Show, Eq, Ord)

-- compare (Finished 1620) (Finished 1490) is GT
-- compare (Finished 1620) Retired is LT
-- minimum [Retired, Finished 1490] is Finished 1490

A derived Ord compares constructors in the order they are declared, then fields from left to right.

Coming from Java: deriving does the work that a Java record does for equals, hashCode, and toString, and also gives an order, which a record does not.

Precisely: derived Show and Read

A derived show adds parentheses where the code would need them, such as Just (Finished 1620), and writes a negative field as Finished (-5). deriving Read writes the reverse: read accepts the text that the derived show writes. Chapter 11 of the Haskell 2010 Report specifies every derived instance.

Experiment

Compare values of two constructors

A derived compare looks at the constructors first. The one listed earlier in the data declaration is smaller. Only for two values with the same constructor does it compare the fields.

Why it matters The order of constructors in a data declaration decides a derived Ord. Put the constructor that should come first in a sort first.

Predict the three printed lines. Then move Retired to the front, data Finish = Retired | Finished Int, and predict again.

data Finish = Finished Int | Retired
  deriving (Show, Eq, Ord)

main :: IO ()
main = do
  print (compare (Finished 1620) (Finished 1490))
  print (compare (Finished 1620) Retired)
  print (minimum [Retired, Finished 1620, Finished 1490])
What to look for

Every stop is in main, a few for each line, because the comparisons run inside the derived instance, which the stepper does not enter. The run prints GT, LT, and Finished 1490. After the edit, it prints GT, GT, and Retired. The first line does not change, because both values are Finished, and their fields are compared. In the second line, Retired is now the smaller value.

Expected output

GT
LT
Finished 1490

04 / Instances

How do you write an instance yourself?

When the derived meaning is wrong for your type, write the instance yourself, as Interfaces, traits, and type classes did for a class of its own. A Show instance defines show. Below, s `mod` 60 is the remainder of s divided by 60, so show (Seconds 1490) gives 24 min 50 s instead of Seconds 1490.

An Ord instance defines compare, which returns LT, EQ, or GT. The class builds <, >, max, min, and the others from it. Below, runners are compared by time, and by name when the times are equal. compare (a, x) (b, y) compares two pairs: the first parts, and the second parts only when the first are equal. Comparing the names too keeps Ord in agreement with the derived ==: compare gives EQ only for two equal runners.

A constraint in a signature asks for an instance. best :: Ord a => [a] -> a, defined as best times = minimum times, can be applied to a [Runner], because Runner has an Ord instance. For a newtype with no Ord instance, GHC reports No instance for (Ord Seconds) arising from a use of ‘best’.

Main.hs
newtype Seconds = Seconds Int

instance Show Seconds where
  show (Seconds s) =
    show (s `div` 60) ++ " min " ++ show (s `mod` 60) ++ " s"

data Runner = Runner String Int
  deriving Eq

instance Ord Runner where
  compare (Runner x a) (Runner y b) = compare (a, x) (b, y)

Write an instance when the derived meaning is wrong; an Ord instance defines compare and must agree with ==.

Precisely: the minimal definition

The Ord class gives every method a default definition built from the others, so an instance must define compare or <=, and may define more. An instance that defines only < compiles with a warning, and the defaults for compare and <= then use each other without end. The Prelude documentation lists each class’s minimal definition.

05 / Num

What does a newtype get from the type it wraps?

A newtype gets nothing from the type it wraps. Metres holds an Int, but Metres has none of Int’s instances. deriving (Show, Eq, Ord) adds those three. Num, the class of +, *, and number literals, cannot be derived in Haskell 2010. deriving Num reports Can’t make a derived instance of ‘Num Metres’: ‘Num’ is not a stock derivable class (Eq, Show, etc.). Without an instance, sum [Metres 400, Metres 800] reports No instance for (Num Metres) arising from a use of ‘sum’.

You can write a Num instance yourself, but Num asks for more than adding. * must also give Metres, so 400 metres times 400 metres is Metres 160000, which is not a distance. fromInteger turns every number literal into Metres, so lap + 5 compiles, and nothing says what the 5 measures. For a unit, write only the functions it needs, such as addMetres below, and keep Num away.

Main.hs
newtype Metres = Metres Int
  deriving (Show, Eq, Ord)

addMetres :: Metres -> Metres -> Metres
addMetres (Metres a) (Metres b) = Metres (a + b)

-- addMetres (Metres 400) (Metres 800) is Metres 1200

A newtype starts with no instances; derive the ones that make sense, and think before you give a unit Num.

Precisely: GeneralizedNewtypeDeriving

The error message names GeneralizedNewtypeDeriving, a GHC extension that is not part of Haskell 2010. With {-# LANGUAGE GeneralizedNewtypeDeriving #-} at the top of the file, deriving Num reuses Int’s instance for Metres. It brings the same * and the same literals as a hand-written instance.

Experiment

Count what a Num instance lets through

A Num instance must give every Num method a meaning for Metres. * returns Metres, so metres times metres is a distance again. fromInteger turns each number literal into Metres, so the 5 in lap + 5 becomes Metres 5 without anyone saying so.

Why it matters A Num instance for a unit accepts products and bare numbers. Write only the functions the unit needs when that matters.

Metres has a hand-written Num instance. Predict the three printed values, and whether sum needs fromInteger. Then delete the instance, lines 4 to 10, and predict what GHC says.

newtype Metres = Metres Int
  deriving (Show)

instance Num Metres where
  Metres a + Metres b = Metres (a + b)
  Metres a * Metres b = Metres (a * b)
  abs (Metres a) = Metres (abs a)
  signum (Metres a) = Metres (signum a)
  fromInteger n = Metres (fromInteger n)
  negate (Metres a) = Metres (negate a)

lap :: Metres
lap = Metres 400

main :: IO ()
main = do
  print (sum [lap, lap, Metres 200])
  print (lap * lap)
  print (lap + 5)
What to look for

Output can show Metres before its number: print writes the constructor before the number inside is computed. For the sum, the stepper stops six times on line 5, Main.+, then twice on line 9, Main.fromInteger, with n = 0: sum starts from the number 0, and the instance turns it into Metres 0. The first line is Metres 1000. lap * lap stops on line 6 and prints Metres 160000. lap + 5 stops on line 5, then on line 9 with n = 5, and prints Metres 405. After the edit, the first error GHC reports is No instance for (Num Metres) arising from a use of ‘sum’.

Expected output

Metres 1000
Metres 160000
Metres 405

06 / In production

What goes wrong with types and instances in real programs?

Four mistakes with types and instances are common in real Haskell code.

Units that mix through a type synonym. type UserId = Int and type OrderId = Int let a function receive an order id where it expects a user id, and the lookup quietly finds the wrong user. Make each a newtype; it costs nothing at run time.

A derived Ord that sorts by the wrong field. sort, from Data.List, puts a list in order with compare. A record declared with the name first sorts by name, whatever the field you meant. The experiment below shows a results list in name order. Put the field that should decide first, or write compare yourself.

An orphan instance. A module that writes instance Show Colour, where Colour comes from a library and Show from the Prelude, declares an orphan instance. When another module does the same, a module that imports both and shows a Colour stops compiling with Overlapping instances for Show Colour. Wrap the library type in a newtype and give the newtype the instance. GHC’s -Worphans option, part of -Wall, warns about orphans.

show used for saving, and read for loading. read turns text back into a value, and read (show x) gives x back while both instances are derived. A hand-written Show for display, such as 24 min 50 s, breaks loading:

Main.hs
newtype Seconds = Seconds Int
  deriving (Eq, Read)

instance Show Seconds where
  show (Seconds s) = show (s `div` 60) ++ " min"

-- read (show (Seconds 1490)) :: Seconds
-- stops with Prelude.read: no parse

Give each unit a newtype, check which field a derived Ord compares first, declare an instance next to its type or class, and keep show for saving apart from display.

Experiment

Sort runners with a derived Ord

The derived compare compares the fields from left to right. With name first, "Ivy" is smaller than "Omar", so the times are never compared. With time first, the times decide, and the names count only when two times are equal.

Why it matters Before you sort a type with a derived Ord, read its fields from left to right: that is the order of the sort.

Omar ran faster than Ivy. Predict the order that sort gives. Then put time first, data Runner = Runner { time :: Int, name :: String }, change lines 8 and 9 to Runner 1490 "Omar" and Runner 1620 "Ivy", and predict again.

import Data.List (sort)

data Runner = Runner { name :: String, time :: Int }
  deriving (Show, Eq, Ord)

main :: IO ()
main = do
  let omar = Runner "Omar" 1490
      ivy = Runner "Ivy" 1620
  print (sort [omar, ivy])
What to look for

The stops are line 7, line 10 twice with omar and ivy unevaluated, then line 8 (Main.main.omar) and line 9 (Main.main.ivy). sort runs inside the library and the derived instance, so it has no stops of its own. The run prints Ivy first: [Runner {name = "Ivy", time = 1620},Runner {name = "Omar", time = 1490}]. After the edit, the stops are the same, and Omar comes first: [Runner {time = 1490, name = "Omar"},Runner {time = 1620, name = "Ivy"}].

Expected output

[Runner {name = "Ivy", time = 1620},Runner {name = "Omar", time = 1490}]

07 / Try it

Can you predict what the derived instances do?

Decide what each derived instance compares, and what the derived show writes.

Main.hs
newtype Metres = Metres Int
  deriving (Show, Eq, Ord)

data Course = Road Metres | Trail Metres
  deriving (Show, Eq, Ord)

main :: IO ()
main = do
  print (maximum [Trail (Metres 8000), Road (Metres 10000)])
  print (Road (Metres 10000) < Road (Metres 9000))

What is printed?

Reveal the reasoning

Trail (Metres 8000), then False. maximum gives the largest element. The derived compare looks at the constructors first, and Trail is listed after Road, so any Trail is larger than any Road, whatever its distance. The derived show writes the value as code, with parentheses around Metres 8000. In the second line both values are Road, so the fields decide: Metres 10000 is not smaller than Metres 9000, because the derived Ord of Metres compares the Ints inside.

Practice tickets

  1. HS-201 · Every skater’s time per kilometre is far too high → Free
  2. HS-202 · White belts are chosen to lead the warm-up → Sign in · Pro
  3. HS-203 · Riders who did not finish top the results board → Sign in · Pro

Keep exploring

Functions as values and higher-order operations →

Primary references