Records
Prerequisites: Algebraic data types
When a constructor has several fields, record syntax names them:
data User = User
{ userName :: String
, userAge :: Int
, userEmail :: String
}
deriving (Show, Eq)
main :: IO ()
main = do
let ada = User {userName = "Ada", userAge = 36, userEmail = "ada@example.com"}
print ada
putStrLn (userName ada)
print (userAge ada + 1)
let older = ada {userAge = 37}
print (userAge older, userAge ada)
User {userName = "Ada", userAge = 36, userEmail = "ada@example.com"}
Ada
37
(37,36)
- Each field name becomes an accessor function:
userName :: User -> String. ada {userAge = 37}is record update syntax — it builds a new record with one field changed. The original is untouched.- The leading-comma layout is the usual style for records.
- You can still construct a record positionally:
User "Ada" 36 "...".
Pattern matching on records
data Product = Product {name :: String, price :: Double, stock :: Int}
describe :: Product -> String
describe Product {name = n, stock = 0} = n ++ ": sold out"
describe p = name p ++ ": " ++ show (price p)
main :: IO ()
main = do
putStrLn (describe (Product "Tea" 3.5 10))
putStrLn (describe (Product "Cake" 4.0 0))
Tea: 3.5
Cake: sold out
Field name clashes
Field names are ordinary top-level functions, so two records in the same
module can't both have a field called name by default. The usual
solutions:
- Prefix field names with the type (
userName,productName), as above — the traditional style. - Enable the
DuplicateRecordFieldsandOverloadedRecordDotextensions, which allow repeated names and the familiaruser.namedot syntax:
{-# LANGUAGE DuplicateRecordFields, OverloadedRecordDot #-}
data User = User {name :: String, age :: Int}
data Pet = Pet {name :: String, species :: String}
main :: IO ()
main = do
let u = User {name = "Ada", age = 36}
p = Pet {name = "Rex", species = "dog"}
putStrLn (u.name ++ " owns " ++ p.name ++ " the " ++ p.species)
Ada owns Rex the dog
{-# LANGUAGE ... #-} at the top of a file turns on a language
extension. Level 3 covers the most useful ones.
Common mistakes
- Records with several constructors where only some have a field: the accessor then crashes on the other constructors. Keep record syntax for single-constructor types, or give each constructor its own record type.