Skip to content

0022: Email Tutorials Haskell For Beginners – Algebraic Data Types with Haskell

Bernard Sibanda edited this page Dec 16, 2025 · 3 revisions

LigerLearn Video – Algebraic Data Types with Haskell

📑 Table of Contents

  • 22.1. From Cartesian Products to Product Types
  • 22.2. Tuples as Product Types in Haskell
  • 22.3. Why a Date as (Int, Int, Int) Is a Bad Idea
  • 22.4. Type Synonyms for Readability
  • 22.5. Type Synonyms vs Type Safety
  • 22.6. Data Type Renamings with newtype
  • 22.7. Why newtype Is Safer Than Synonyms
  • 22.8. When to Use: Tuple vs Type Synonym vs newtype
  • 22.9. Glossary of Terms
  • 22.10. Multiple Choice Questions (with Answers)

22.1. From Cartesian Products to Product Types

We start with a bit of maths:

  • Given two sets A and B, the Cartesian product A × B is the set of all possible pairs (a, b) where a ∈ A and b ∈ B.

  • Example:

    • A = {1, 2, 3}
    • B = {1, 2, 3}
    • A × B = { (1,1), (1,2), (1,3), (2,1), …, (3,3) }

In types:

  • Think of each type as a set of possible values.
  • When we combine types into a tuple, the possible values are the product of the options from each type.

That’s why we call them product types:

The number of possible values is the product of the number of possibilities of each component type.

22.2. Tuples as Product Types in Haskell

Example:

x :: (Int, Int)
  • x has the type “pair of Int and Int”.
  • It can be any combination of two Int values: (0, 0), (1, 42), (-5, 999), etc.

Tuples are built-in product types in Haskell:

  • Written in types: (A, B, C, …)
  • Written in values: (a, b, c, …)
  • Elements are separated by commas and enclosed in parentheses.

The set of possible values of (Int, Int) is the Cartesian product of all possible Int values with themselves.

22.3. Why a Date as (Int, Int, Int) Is a Bad Idea

A common beginner idea:

-- (day, month, year)
date :: (Int, Int, Int)
date = (1, 1, 2025)

Yes, it works, but:

  1. The type (Int, Int, Int) is meaningless on its own.

    • It doesn’t tell you this triple represents a date.
  2. No indication of order.

    • Is it (day, month, year) or (year, month, day) or something else?
  3. No validation at the type level.

    • (31, 31, 2025) is allowed.
    • Negative days or months are allowed.
    • The compiler cannot help you catch invalid dates.

This motivates richer types instead of raw tuples.

22.4. Type Synonyms for Readability

Type synonyms let us give better names to existing types.

Example: String is defined as:

type String = [Char]

So String is not a new type – it’s just another name for [Char].

We can improve the readability of our date types:

type Day   = Int
type Month = Int
type Year  = Int

type Date  = (Day, Month, Year)

Now a type signature like:

birthday :: Date
birthday = (1, 1, 2000)

is much clearer than:

birthday :: (Int, Int, Int)

The meaning (“this is a date”) becomes explicit.

22.5. Type Synonyms vs Type Safety

Important: Type synonyms do not create new types.

  • Day, Month, and Year are all still just Int underneath.
  • The compiler treats Day, Month, Year, and Int as the same type.

That means the following is still allowed:

weirdDate :: Date
weirdDate = (2025, 31, 1)  -- year in day position, invalid month, etc.

The compiler happily accepts this:

  • It only sees (Int, Int, Int), which is fine from its point of view.
  • There is no protection against mixing up day/month/year or using impossible values.

So:

  • ✅ Type synonyms → improve readability
  • ❌ Type synonyms → do not improve type safety

22.6. Data Type Renamings with newtype

To keep readability and improve safety, we can use newtype.

newtype creates a new distinct type that has the same runtime representation as an existing type, but is treated as different by the type checker.

Example:

newtype Day   = Day   Int
newtype Month = Month Int
newtype Year  = Year  Int

Each has:

  • A type constructor (Day, Month, Year)
  • A data constructor with the same name that wraps an Int

Now our Date could be:

type Date = (Day, Month, Year)

someDate :: Date
someDate = (Day 1, Month 1, Year 2025)

To construct values, we must use the constructors:

  • Day 1
  • Month 1
  • Year 2025

This is different from plain type synonyms, where you use raw Ints without constructors.

22.7. Why newtype Is Safer Than Synonyms

Consider this invalid date as before:

-- With type synonyms, this compiles:
badDate :: Date
badDate = (2025, 31, 1)

With newtype:

type Date = (Day, Month, Year)

badDate :: Date
badDate = (Month 1, Day 31, Year 2025)  -- swapped day/month

Now the compiler complains:

  • It expected Day but got Month
  • It expected Month but got Day

So newtype:

  • Forces you to wrap values correctly (Day 5, Month 12, etc.)
  • Makes wrong positions or swaps a type error
  • Still has zero runtime overhead (same representation as the wrapped type)

Restriction of newtype:

  • Exactly one constructor
  • That constructor has exactly one field

That’s why it’s called data type renaming: you are essentially taking an existing type and giving it a new, safer “wrapper type”.

22.8. When to Use: Tuple vs Type Synonym vs newtype

Use a raw tuple like (Int, Int) when:

  • It’s very local/simple.
  • The meaning is obvious.
  • You don’t need extra semantic clarity.

Use a type synonym when:

  • You want clearer documentation of what a type means.
  • You don’t strictly need additional safety.
  • Example: type Username = String

Use newtype when:

  • You want both clarity and stronger type safety.
  • You want to prevent mixing values that are all Int/String but represent different concepts.
  • Example: newtype UserId = UserId Int, newtype ProductId = ProductId Int.

22.9. Glossary of Terms

  • Algebraic Data Type (ADT) A type built from other types using sum (choices) and product (combinations) constructions.

  • Product Type A type where values are combinations of other types (like tuples).

  • Cartesian Product All possible pairs (a, b) from sets A and B. Forms the mathematical basis for product types.

  • Type Synonym A new name for an existing type, created with the type keyword. No new type is created.

  • newtype A way to define a new, distinct type that wraps exactly one existing type, with the same runtime representation but stronger type safety.

  • Constructor A function-like name used to build values of a given type (e.g. Day 1, Month 12).

22.10. Multiple Choice Questions (with Answers)

Q1. What is a product type in Haskell?

A. A type with only one possible value B. A type defined as a list of values C. A type whose possible values are combinations of other types (like tuples) D. A type used only for numbers

Answer: C

Q2. Which of the following best describes a type synonym?

A. It creates a completely new type with its own representation. B. It is another name for an existing type and shares the same representation. C. It automatically validates all values at runtime. D. It can only be used with numeric types.

Answer: B

Q3. What is the main problem with using (Int, Int, Int) to represent a date?

A. Haskell cannot compile tuples of three Ints. B. Tuples of Ints are too slow at runtime. C. The type does not indicate which Int is day, month, or year, and allows invalid combinations. D. Haskell does not support three-element tuples.

Answer: C

Q4. What does a newtype declaration do?

A. Creates a new type with a different runtime representation from the original type. B. Creates a new type that is identical in the type system and runtime to the original type. C. Creates a new, distinct type in the type system, but with the same runtime representation as the wrapped type. D. Only renames an existing value, not a type.

Answer: C

Q5. Why might you prefer newtype over a plain type synonym?

A. newtype is faster at runtime. B. newtype is required to define any function. C. newtype gives a new distinct type, preventing mixing up different concepts that share the same underlying type. D. newtype automatically converts invalid data into valid data.

Answer: C

Quizz & Progress Badge NFT

Clone this wiki locally