Skip to content
AgileLogics
5 min read

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 DuplicateRecordFields and OverloadedRecordDot extensions, which allow repeated names and the familiar user.name dot 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.