Skip to content

Haskell Records Guide — Record Syntax, Field Accessors, and Pattern Matching

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Haskell Records Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Haskell records define data types with named fields using curly braces, generating accessor functions automatically -- supporting record pattern matching, record update syntax with { field = value }, and NamedFieldPuns for concise construction.

Defining Records

-- Record syntax
data Person = Person
    { name   :: String
    , age    :: Int
    , email  :: String
    } deriving (Show, Eq)

-- Constructor with records
alice = Person
    { name   = "Alice"
    , age    = 30
    , email  = "alice@example.com"
    }

-- Accessor functions
getName alice  -- "Alice"
getAge alice   -- 30

Record Pattern Matching

-- Pattern matching on records
greet :: Person -> String
greet Person { name = n, age = a } =
    "Hello, " ++ n ++ " (age " ++ show a ++ ")"

-- Field puns (GHC extension)
{-# LANGUAGE NamedFieldPuns #-}
greet' Person { name, age } =
    "Hello, " ++ name ++ " (age " ++ show age ++ ")"

Record Updates

-- Update specific fields (creates new record)
bob = Person { name = "Bob", age = 25, email = "bob@test.com" }
bobOlder = bob { age = 26 }

-- Chained updates
updated = bob { age = 26, email = "bob@new.com" }

-- Using with accessors
incrementAge p@Person { age } = p { age = age + 1 }

Partial Records

-- Records can be used in sum types
data Shape
    = Circle    { radius :: Float }
    | Rectangle { width :: Float, height :: Float }
    deriving (Show)

-- Partial accessor: radius on Rectangle is undefined at runtime
-- Use pattern matching instead:
area :: Shape -> Float
area Circle { radius }      = pi * radius ^ 2
area Rectangle { width, height } = width * height

Common Mistakes

1. Duplicate field names

Field names must be unique within a module. Two types can't share the same field name without extensions.

2. Partial accessor functions

radius (Rectangle 3 4) compiles but crashes at runtime. Only use accessors when the constructor is guaranteed.

3. Forgetting that update creates a copy

Record update creates a new value. For large data structures, this is O(1) due to immutability (structural sharing).

Practice Questions

1. How do you access a record field? Using the generated accessor function: name person returns the name field. Period syntax: person.name.

2. How do you update a record field? person { name = "New Name" } creates a copy with the updated field. Original is unchanged.

3. What happens with duplicate field names in records? The module's export list must resolve conflicts. GHC's DisambiguateRecordFields extension can help.

FAQ

{{< faq question="Are record accessors just functions?" >}} Yes. name :: Person -> String. They work with all higher-order functions: fmap name [alice, bob]. {{< /faq >}}

{{< faq question="Can I use lenses with records?" >}} Yes. The lens library (Control.Lens) provides composable getters and setters: view name person, set age 31 person. {{< /faq >}}

{{< faq question="What is record wildcard syntax?" >}} Person { .. } matches all fields and brings them into scope. Person { name, .. } matches some fields explicitly. {{< /faq >}}

What's Next

Now learn about typeclasses.

Topic Description Link
Typeclasses Typeclass basics {{< ref "14-typeclasses" >}}
Custom Types Defining algebraic data types {{< ref "15-custom-types" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro