Record types

The define-record-type form can be used for creating new data types, called record types. A predicate, constructor, and field accessors and modifiers are defined for each record type. The define-record-type feature is specified by SRFI-9, which is implemented by many modern Scheme implementations.

Syntax: define-record-type type-name (constructor-name field-tag ...) predicate-name (field-tag accessor-name [modifier-name]) ...

The form define-record-type is generative: each use creates a new record type that is distinct from all existing types, including other record types and Scheme’s predefined types. Record-type definitions may only occur at top-level (there are two possible semantics for ‘internal’ record-type definitions, generative and nongenerative, and no consensus as to which is better).

An instance of define-record-type is equivalent to the following definitions:

  • The type-name is bound to a representation of the record type itself.

  • The constructor-name is bound to a procedure that takes as many arguments as there are field-tags in the (constructor-name ...) subform and returns a new type-name record. Fields whose tags are listed with constructor-name have the corresponding argument as their initial value. The initial values of all other fields are unspecified.

  • The predicate-name is a predicate that returns #t when given a value returned by constructor-name and #f for everything else.

  • Each accessor-name is a procedure that takes a record of type type-name and returns the current value of the corresponding field. It is an error to pass an accessor a value which is not a record of the appropriate type.

  • Each modifier-name is a procedure that takes a record of type type-name and a value which becomes the new value of the corresponding field. The result (in Kawa) is the empty value #!void. It is an error to pass a modifier a first argument which is not a record of the appropriate type.

Set!ing the value of any of these identifiers has no effect on the behavior of any of their original values.

Here is an example of how you can define a record type named pare with two fields x and y:

(define-record-type pare
  (kons x y)
  (x kar set-kar!)
  (y kdr))

The above defines kons to be a constructor, kar and kdr to be accessors, set-kar! to be a modifier, and pare? to be a predicate for pares.

(pare? (kons 1 2))        ⇒ #t
(pare? (cons 1 2))        ⇒ #f
(kar (kons 1 2))          ⇒ 1
(kdr (kons 1 2))          ⇒ 2
(let ((k (kons 1 2)))
  (set-kar! k 3)
  (kar k))                ⇒ 3

Kawa compiles the record type into a nested class. If the define-record-type appears at module level, the result is a class that is a member of the module class. For example if the above pare class is define in a module parelib, then the result is a class named pare with the internal JVM name parelib$pare. The define-record-type can appear inside a procedure, in which case the result is an inner class.

The nested class has a name derived from the type-name. If the type-name is valid Java class name, that becomes the name of the Java class. If the type-name has the form <name> (for example <pare>), then name is used, if possible, for the Java class name. Otherwise, the name of the Java class is derived by "mangling" the type-name. In any case, the package is the same as that of the surrounding module.

Kawa generates efficient code for the resulting functions, without needing to use run-time reflection.