Step 1 of 7 · Reading · ~3 min
Learn
Custom Types
Algebraic data types are how Haskell models domain data. The data keyword creates new types with sum (alternatives) and product (combinations) shapes. LYAH treats this as foundational; Real World Haskell uses it from chapter 3 onward.
Sum types — alternatives
data Color = Red | Green | Blue
Color is a new type with three constructors — Red, Green, Blue. They take no arguments — just enum-like tags.
favorite :: Color
favorite = Blue
describe :: Color -> String
describe Red = "warm"
describe Green = "natural"
describe Blue = "cool"
Pattern matching is exhaustive — the compiler warns if a case is missing.
Constructors with data
data Shape = Circle Double | Square Double | Rectangle Double Double
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Square s) = s * s
area (Rectangle w h) = w * h
Each constructor takes its own arguments. Circle 5 is a Shape; so is Rectangle 3 4. Pattern matching destructures them.
This is a sum of products — Shape is one of three (sum), each with its own fields (product).
Records
When a constructor has many fields, named accessors help:
data Person = Person { name :: String, age :: Int, email :: String }
ada :: Person
ada = Person { name = "Ada", age = 36, email = "[email protected]" }
-- Accessors are auto-generated:
name ada -- "Ada"
age ada -- 36
-- Update syntax — produces a new Person:
older = ada { age = 37 }
Record syntax generates name :: Person -> String etc. for free.
Type parameters (generics)
data Maybe a = Nothing | Just a
The a is a TYPE PARAMETER. Maybe Int is one type, Maybe String is another. Pattern match:
safeDivide :: Int -> Int -> Maybe Int
safeDivide _ 0 = Nothing
safeDivide a b = Just (a `div` b)
case safeDivide 10 0 of
Just n -> putStrLn ("got " ++ show n)
Nothing -> putStrLn "divide by zero"
Maybe is built-in; this is just showing how it would be defined.
Recursive types
data Tree a = Leaf | Node a (Tree a) (Tree a)
depth :: Tree a -> Int
depth Leaf = 0
depth (Node _ l r) = 1 + max (depth l) (depth r)
A Tree of a is either a Leaf or a Node with a value and two subtrees. Recursive — the (Tree a) references itself.
Deriving — automatic instances
data Point = Point Int Int deriving (Show, Eq, Ord)
deriving auto-generates instances for common type classes:
Show— printable (show p)Eq— equality (==,/=)Ord— ordering (<,>)Read— parseable from StringEnum— successor/predecessorBounded— minBound/maxBound
Without deriving Show, print p won't compile. Almost always include Show, Eq at minimum.
When to use ADTs
- Domain modeling:
data Status = Active | Pending | Archived - Result types:
data Result a = Ok a | Err String - AST nodes:
data Expr = Lit Int | Add Expr Expr | Mul Expr Expr - State machines:
data Order = Cart | Confirmed | Shipped | Delivered
ADTs + pattern matching = type-safe state machines. Add a new variant; the compiler tells you every match site that needs updating.
Common mistakes
- Forgetting
deriving Show—printand many other things won't compile. - Constructor name shadows type name —
data Foo = Foo Intis fine but confusing initially. Constructor and type can share names. - Capitalization — type names AND constructor names are uppercase. Type variables (the
ainMaybe a) are lowercase. - Mixing Maybe with
Nothing—Nothingis the constructor;Maybeis the type. UseMaybe Int -> Maybe Intin signatures. - Plain enums when records would clarify — for things with multiple fields, records are more maintainable than positional constructors.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…