-
Notifications
You must be signed in to change notification settings - Fork 4
0022: Email Tutorials Haskell For Beginners – Algebraic Data Types with Haskell
LigerLearn Video – Algebraic Data Types with Haskell
- 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
newtypeIs 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)
We start with a bit of maths:
-
Given two sets
AandB, the Cartesian productA × Bis the set of all possible pairs(a, b)wherea ∈ Aandb ∈ 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.
Example:
x :: (Int, Int)-
xhas the type “pair ofIntandInt”. - It can be any combination of two
Intvalues:(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.
A common beginner idea:
-- (day, month, year)
date :: (Int, Int, Int)
date = (1, 1, 2025)Yes, it works, but:
-
The type
(Int, Int, Int)is meaningless on its own.- It doesn’t tell you this triple represents a date.
-
No indication of order.
- Is it
(day, month, year)or(year, month, day)or something else?
- Is it
-
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.
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.
Important: Type synonyms do not create new types.
-
Day,Month, andYearare all still justIntunderneath. - The compiler treats
Day,Month,Year, andIntas 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
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 IntEach 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 1Month 1Year 2025
This is different from plain type synonyms, where you use raw Ints without constructors.
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/monthNow the compiler complains:
- It expected
Daybut gotMonth - It expected
Monthbut gotDay
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”.
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/Stringbut represent different concepts. - Example:
newtype UserId = UserId Int,newtype ProductId = ProductId Int.
-
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 setsAandB. Forms the mathematical basis for product types. -
Type Synonym A new name for an existing type, created with the
typekeyword. No new type is created. -
newtypeA 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).
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
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
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
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
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
Bernard Sibanda is a global Technology Entrepreneur, Web3 and Software Consultant with a deep focus on Cardano Blockchain, Midnight and Community building.
Key Positions:
- Founder, CTO, Developer Advocate cohort #1, Fullstake Developer, Cardano Ambassador, Catalyst Project Manager, DREP-WIMS:
- Co-founder of ABL Tech and Cardano Africa Live
- EBU-certified Plutus Pioneer (Plutus/Haskell)
- Cohort #1 Plutus Pioneer Developer
- Catalyst Community Reviewer & Funded Projects Manager
-
DRep for WIMS-Cardano (ID:
drep1yguj8zu48n99pv70yl6ckzt9hdgjy8yjnlqs2uyzcpafnjgu4vkul) - Intersect Developer Advocate
- Intersect Committe Member 2025-2026
- Cardano Marketer,Promoter and blogger
- Cardano Open Source Contributor
- Cardano communities and events organizer and builder
- Cardano Ambassador for South Africa
Official links:
- Stablecoins Dex
- Coxygen Global Universities
- WIMS Cardano Global
- Cardano Africa Live
- WIMS Cardano Videos
- Cardano Smart Contract Videos
- Fullstack IT Consulting
Social links: