Violet

← Docs

Records

A record declaration introduces a type together with projection per field, and the type satisfies a definitional eta rule so two records should be equal exactly when their fields are.

Declaration

A record is declared with \record, followed by the type and one | field : T line per field.

\record Point : 𝓤
  | x : Nat
  | y : Nat

Each field becomes a qualified projection: the declaration above gives you Point/x : Point -> Nat and Point/y : Point -> Nat, the same convention used for inductive constructors.

Construction

Build a record by giving every field a value. Order of fields in the literal does not matter — they are matched by name.

\let origin : Point => { x => Nat/zero | y => Nat/zero }

Punning

When a variable already in scope shares a field name, you can drop the => value part. { x | y } desugars to { x => x | y => y }.

\let pt (x : Nat) (y : Nat) : Point => { x | y }

Function fields

When a field's type is a function, you can name its parameters to the left of => instead of writing an explicit lambda — field p1 … pn => body desugars to field => \p1 … pn -> body. The parameters' type (and any implicits) are recovered from the field's declared type.

\record Endo : 𝓤
  | run : Nat -> Nat

# these two are the same record
\let inc : Endo => { run n => Nat/suc n }
\let inc' : Endo => { run => \n -> Nat/suc n }

Field projection

Read fields by applying the qualified projection — the same pattern as Nat/suc on an inductive type.

\let origin-y : Nat => Point/y origin

Or we can use dot syntax

\let origin-y : Nat => origin.y

Update

Records are immutable, but you can build a new record from an existing one by overriding some fields. The syntax mirrors a record literal with a base before the \with keyword:

\let update-x : Point => { origin \with x => Nat/suc Nat/zero }

Fields that are not mentioned are copied from the base. List multiple overrides separated by |, and punning works just like in a plain literal — { origin \with x } means { origin \with x => x }:

\let update-both : Point =>
  { origin \with x => Nat/suc Nat/zero | y => Nat/suc (Nat/suc Nat/zero) }

\let bump-x (x : Nat) : Point => { origin \with x }

At least one field is required — { origin \with } is rejected — and every name after \with must already be a field of the record's type.