Record
The record data type in Filtrera allows you to group related values, including functions, into a single entity. This reference guide provides a detailed specification of the record data type, its syntax, and usage for users already familiar with the concept.
Records in Filtrera are collections of key-value pairs where each key is a field name and each value can be any valid Filtrera data type, including functions. Records are immutable, meaning their values cannot be changed once set. However, records can be cloned and modified using the with keyword.
Type Notation for Records
The type notation for records is:
{ <field name>: <type> }Example
let Person: { name: text, age: number }In this example, Person is an type with two fields: name of type text and age of type number.
Multiple fields can be separated by commas on a single line, or as an indented block with one field per line.
Example
let Person: { name: text, age: number}Using PascalCase for type symbols is a convention in Filtrera. Defining record types in this way ensures value compatibility without needing to type the full record type everywhere with risk of mistakes.
Empty Record Type
An empty record type can be defined using { }. When used as a parameter, it essentially means “any record”.
Example
param obj: { field: text } | nothingfrom obj match { } |> obj.field |> 'obj was nothing'Optional Fields
By default every field declared in a record type is required: a value is only assignable to the type if it has every declared field. Mark a field with ? after the field name to declare it as optional, meaning the field may be omitted entirely.
let User: { id: text email?: text}In this example, id is required but email is optional.
Example: literal records
Both of the records below satisfy the User type — the second one omits the optional email field.
let bob: User = { id = 'b' }A required field cannot be omitted. The following fails to type-check because id is required:
Example: pattern matching on optional fields
A pattern that names an optional field only matches when the field is present.
param user: { id: text, email?: text }
from user match (u: { email: text }) |> $'email is {u.email}' |> $'no email for {user.id}'In this example, the first arm matches users that have an email field. Users without email fall through to the second arm.
Optional vs. nullable
field?: T and field: T | nothing look similar but mean different things:
field?: T— the field may be absent from the record. When present, its value isT.field: T | nothing— the field is always present, but its value can beTornothing.
This distinction matters for contract-style APIs that need to tell “no change” (field absent) apart from “explicitly cleared” (field set to nothing).
param updates: { name: text | nothing // required; nothing means "explicitly cleared" email?: text // optional; absence means "no change"}When a record is serialized to JSON, an absent optional field is omitted from the output, while a field whose value is nothing is serialized as null.
Records are Maps
All records are also maps with a text key. This makes it possible to pass an record anywhere a map with text key is required:
let func (map: { text -> value }) => map keysfrom func { key1 = 1 key2 = 2 key3 = 3}// Returns ['key2', 'key1', 'key3']Literal Record Notation
Literal records are defined using the following syntax:
{ <field name> = <value> }Example
let person = { name = 'Alice', age = 30 }In this example, person is an record with two fields: name set to 'Alice' and age set to 30.
Accessing Record Fields
Record fields can be accessed using the period (.) operator.
Example
let person = { name = 'Alice', age = 30 }let personName = person.namelet personAge = person.age
from personNamefrom personAgeIn this example, personName is 'Alice' and personAge is 30.
Record Fields Containing Functions
Records can contain functions as fields. Functions in Filtrera require at least one parameter. If you need a constant value, use a regular field instead.
Example
let person = { name = 'Alice', age = 30, greet = (x) => 'Hello, ' + person.name}
let greeting = person.greet(1)
from greetingIn this example, person contains a function greet that returns a greeting message using the name field. Note that functions must have at least one parameter.
Immutability and Cloning
Records in Filtrera are immutable. Once created, their values cannot be changed. However, records can be cloned and modified using the with keyword. For more detailed information on cloning and modifying records, refer to the with article.
Example
let person = { name = 'Alice', age = 30 }let updatedPerson = person with { age = 31 }
from updatedPersonIn this example, updatedPerson is a clone of person with the age field updated to 31.
Practical Usage
Example: Complex Record with Multiple Fields
let Company: { name: text, founded: number, founder: { name: text, age: number }, employees: [{ name: text, age: number }]}
let company: Company = { name = 'Tech Corp', founded = 2005, founder = { name = 'Bob', age = 50 }, employees = [ { name = 'Alice', age = 30 }, { name = 'Charlie', age = 25 } ]}
let companyName = company.namelet founderName = company.founder.namelet firstEmployeeName = company.employees first name
from companyNamefrom founderNamefrom firstEmployeeNameIn this example, Company is an record type containing nested Records and Lists, demonstrating the flexibility and power of the record data type in Filtrera. Note that the first filter is used to access the first item in the employees iterator.
Summary
The record data type in Filtrera allows you to create complex and structured data by grouping related values into a single entity. By understanding its syntax and usage, including type notation, literal record notation, and immutability, you can leverage records to create expressive and functional programs. The ability to clone and modify records using the with keyword further enhances the flexibility and robustness of your Filtrera programs. Using PascalCase for type symbols and defining record types helps ensure value compatibility and reduces redundancy.