Skip to content

Records and Interfaces

Use ELPS record types to keep data and the methods that change it together, with checks on every change.

A record type defines objects with named, typed fields and with methods that change them. The type checks every field when an object is made and every change a method makes. A failed method leaves the object as it was before the call.

Use a record type for a business object with rules, for example an account that must never go below zero. Use a validator for data that you only read, such as a request body.

Record types are part of the schema package (s:), from ELPS v1.95.0. Every example on this page runs in your browser. Edit an example to see the new result.


Define a record type

(set 'type-name
  (s:make-record "type-name"
    field...
    method...
    :check check-function))
  • A field is (s:has-key "name" type...) or, for a field that can be left out, (s:may-have-key "name" type...). The types are the ones a validator takes.
  • A method is (s:method name (receiver args...) body...). The receiver is the object the method is called on.
  • :check is optional. It is a function of the object that raises an error when the object breaks a rule.
  • Bind the type with set at the top level of a file.

Call the type to make an object. Call the object with a quoted name to read a field or call a method.

You can also make an object from a sorted map, for example a JSON object: (account (json:load-string "...")). The type checks each field, so a request with a bad field fails there.


Method calls are atomic

A method call works like a database transaction. If the method raises an error, or :check refuses the object after it, every field goes back to its value before the call. Code that catches the error still has a valid object.

The rollback covers one object. If a method changes two objects and then fails, the other object keeps its change. A phylum endpoint that raises an error already rolls back its whole transaction, so the rollback matters when your code catches an error and keeps going.


Rules for changes

Rule Why
Change a field only with s:set-field!, in a method of the object's type So every change goes through the type's checks
Do not change a field in :check :check only reads the object
Add a method later with s:defmethod only at the top level of a file The type must be complete when the phylum finishes loading
Build a new value and set it, do not change a field's value in place (for example with assoc!) The type cannot see or undo a change in place

A call that breaks one of the first three rules raises record-error. The ELPS lint checks, such as record-set-outside-method, report these mistakes before the code runs.


Typed arguments

Write a method argument as [name type] to check it before the body runs. A bad call changes nothing. :returns type, after the arguments, checks the value the method returns.

Call (w 'top-up -5) alone to see the error. It names the method and the argument.

Argument Checked
[amount positive] Always
&optional [memo s:string] When the call gives it
&key [fee s:number] When the call gives :fee
&rest [xs s:number] Each item

Use [other :self] for an argument of the type you define, and [to 'payee] for a type that is defined later in the file.


Interfaces

An interface names a set of methods. An object satisfies an interface when its type has each method, with arguments that accept each call the interface allows. A type does not need to say which interfaces it satisfies.

Use an interface when code must work with more than one record type. This example pays any object that has a pay method:

An interface is a validator. Use it as the type of a field, (s:has-key "payer" payable), or of a list, (s:of payable).

To require a type to satisfy an interface, add :implements (payable) to s:make-record. A method that does not fit then fails when the type is defined.


Use records with match

match can test the type of an object and bind its fields:


Other functions

Function Returns
(s:fields obj) A copy of the object's fields as a sorted map
(s:record? value) true when the value is an object of a record type
(s:defmethod type name (receiver args...) body...) Adds a method to a type, at the top level of a file
(s:ref type) A type that passes an object of type and does not check its fields. Use it only for objects your own code made

We use cookies to give you the best experience of using this website. By continuing to use this site, you accept our use of cookies. Please read our Cookie Policy for more information.