3.15. Records

3.15.1. Simple Records

3.15.1.1. Record Declaration

Records are just inductive types with one nameless constructor. Therefore it is sufficient to list the arguments with their names.

General form of a record definition:

record type
    Name <params>: Sort     -- The optional sort is 'Prop' or 'Any'
:=                          -- If ommitted 'Any' is used.
    field: Typefield: Type₂
    ...

-- equivalent inductive type
type
    Name <params>: Sort
:=
    _: all (field: Type₁) (field: Type₂) ... : Name
--  ^  nameless constructor

For a record definition the compiler declares the field accessor functions

Name.field: Name <params> → TypeName.field: Name <params> → Type₂
...

The field accessor functions are declared in the namespace of the record.

E.g. the declaration

record type Person :=
    firstName: String
    lastName: String
    age: Int

defines the algebraic type

type Person :=
    mk: String -> String -> Person
--  ^ just an arbitrary constructor name chosen

with the additional functions

Person.firstName: Person -> String := case
    λ (mk name _ _) := name

Person.lastName: Person -> String := case
    λ (mk _ name _) := name

Person.age: Person -> String := case
    λ (mk _ _ age) := age

3.15.1.2. Record Expressions

There are no constructor names for records. But there are record expressions which allow the construction or record objects.

record ["John", "Boy", 5]           -- exact order, like a list.

record {age := 5, lastName := "Boy", firstName := "John"}
    -- arbitrary order, like a set.

In case of ambiguity we add a type or a type annotation

record ["Billy", "Boy", 3]: Person

Updating record fields is nondestructive and is done with the syntax:

record {person; age := age person + 1}

This expression creates a copy of the record person with the field age updated. One or more fields can be updated.

3.15.1.3. Record Pattern Match

Since records are just a special kind of inductive types, they can be pattern matched. Because of the absence of a constructor name, record expressions of the form record [field₁, field₂, ...] can be used as pattern.

E.g. with the record

record type Refine {A: Any} (P: AProp) :=
    value: A
    proof: P value

we can pattern match

f {A: Any} {P: AProp}
: Refine PT
:= case
    (record [v, prf]) :=
        expr            -- any expression using 'v' and 'prf'
                        -- and returning a 'T'
-- with named fields
:= case
    (record {value, proof}) := ...   -- field names and pattern
                                    -- variables identical

:= case
    (record {value := v; proof := prf}) := ...
        -- field names and pattern variables different

3.15.2. Dependent Records

Records can have dependent types

record type Sigma {A: Any} (P: AProp) :=
    value: A
    proof: P value

-- corresponding inductive type
type Sigma {A: Any} (P: A -> Prop) :=
    _ x: P x -> Sigma

-- field accessor functions
value {A: Any} {P: AProp}: Sigma PA := case
    λ record [x, _] := x

proof {A: Any} (P: AProp}: all (s:Sigma P) → P (value s) := case
    λ record [_, p] := p

Field update of dependent records have to be done consistently. I.e. if the type of one field depends on the value of another field, then both have to be updated.

It is possible to mark some fields as implicit. Then the corresponding values in record constructions or record updates have to be ommitted or marked as implicit.