Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Records

A record type defines named, typed fields. Record objects have reference semantics. All operations on this page are Controls.

ControlResult
field(name, type)Field descriptor
record_type(name)No value
record(name, fields...)No value
record_of(type, values...)New record
get_field(record, names...)Selected value
set_field(record, names..., value)No value

field

Builds a field descriptor from a literal identifier and an evaluated type description. The descriptor is consumed by record; it is not a runtime value. A non-identifier name or a non-type second argument fails.

record

Defines a named record type. The name is a literal identifier. Field expressions are evaluated from left to right and must return field descriptors. Field order is the order supplied here, and every field name must be unique.

record(Person, field(name, string), field(age, integer))
declare(person, Person, record_of(Person, "Ada", 37))
print(get_field(person, name)) / Ada

A record can have zero fields: record(Empty) defines it and record_of(Empty) constructs it. The type name is available to later type expressions. Types are registered in the runtime, not confined to a block’s variable scope.

Defining an already defined record, conflicting with an existing non-record type, supplying duplicate field names, or supplying a non-field descriptor fails. A failed definition can leave its record name declared but undefined; there is no rollback of registration.

record_type

Declares an undefined record type so it can be referenced by other type expressions before its fields are defined. record can later complete it. This supports recursive and mutually referring record types.

record_type(Node)
record(Node, field(value, integer), field(next, optional(Node)))
declare(tail, Node, record_of(Node, 2, none))
declare(head, Node, record_of(Node, 1, tail))
print(get_field(head, next, value)) / 2

The name must be a literal identifier and must not already be declared. An undefined record type cannot be used to create an object until record defines its fields. Optional links allow recursive structures to terminate; see optionals.

record_of

Evaluates a record type, then its value expressions from left to right. Creates an object with one value per field in declaration order. The count must match exactly, and each value must be accepted by the corresponding field type. A non-record type or an undefined record type fails.

record(Contact, field(name, string), field(phone, optional(string)))
declare(contact, Contact, record_of(Contact, "Ada", none))
print(to_string(contact)) / {"name":"Ada","phone":null}

Optional fields still need explicit arguments. Omission and none are not the same thing.

get_field

Evaluates the record expression once, then follows one or more literal field identifiers in order. Field names are not evaluated as variables and must not be quoted. Every intermediate value must be a record.

record(Address, field(street, string))
record(Person, field(address, Address))
declare(person, Person, record_of(Person, record_of(Address, "Main Street")))
print(get_field(person, address, street)) / Main Street

A missing field, non-identifier field name, or non-record along the path fails. An absent optional link is not traversed automatically. The final result can be any value, including none or a reference to another object.

Use get_at for nested arrays and chain for mixed traversal:

record(Address, field(street, string))
record(Person, field(addresses, array(Address)))
declare(person, Person, record_of(Person, array_of(record_of(Address, "Main Street"))))
print(chain(link, person,
    get_field(link, addresses),
    get_at(link, 0),
    get_field(link, street)
)) / Main Street

set_field

Follows literal field names with the same rules as get_field, then replaces the final existing field. Evaluates the record once and validates the entire path before evaluating the new value. The value must be accepted by that field’s declared type. It does not create fields or intermediate records.

record(Address, field(street, string))
record(Person, field(address, Address))
declare(person, Person, record_of(Person, record_of(Address, "Main Street")))
set_field(person, address, street, "Oak Street")
print(get_field(person, address, street)) / Oak Street

An invalid path skips the value expression. A type mismatch is checked after that expression has run. Mutation affects the existing object and is visible through its other references. Produces no value; use a block ending in the object if a chain step also needs to return it.