This site is a static rendering of the Trac instance that was used by R7RS-WG1 for its work on R7RS-small (PDF), which was ratified in 2013. For more information, see Home. For a version of this page that may be more recent, see AggregatesMedernach in WG2's repo for R7RS-large.


2011-01-15 01:54:57
6Field specification (mutable) added, optional inheritance, implementation addedhistory


Rationale [see 1]

Declaring and generating fixed sized data type disjoint from all other types, called AGGREGATES.

General other record or object features may be build on top of these aggregates.

Datatype and associated functions [see 3]

(define-datatype <datatype-name> <datatype-info> <designation> (<fieldname-spec> ...))

Returns a new datatype. designation is a description of the datatype. (<fieldname-spec> ...) is a list of field specification. A field specification is either:

It is an error to have duplicated fieldnames.

Field inheritance is optional and could be provided on top of this proposal with the following syntax:

(define-inherited-datatype <datatype-name> <datatype-info> <parent-datatype-info> <designation> (<fieldname-spec> ...))

Corresponding testing implementations are available at AggregatesMedernachImplementation and AggregatesMedernachInheritanceImplementation.

(datatype-info->designation <datatype-info>)

Returns type designation of type described by <datatype-info>.

(datatype-info->fields <datatype-info>)

Returns corresponding list of fields of type described by <datatype-info>.

Aggregates ( constructor / accessor ) constructor

(create-aggregate-functions <datatype>)

If <datatype> is of datatype kind, then returns corresponding constructor, switch function (see below) and mutators. <datatype> ensures that the call to create-aggregate-functions is functional, the same aggregate functions are generated if applied to the same <datatype>.

create-aggregate-functions returns 3 functions as values:

make-<aggregate> is a function taking a fixed number of arguments as specified by the <datatype> and returning a new aggregate containing these arguments as aggregate components.

<aggregate>-switch is explained below.

<aggregate>-mutators is an association list from fields symbols to corresponding mutators for fields specified as mutators. A mutator function takes 2 arguments: (<mutator> <obj> <val>) sets corresponding field of <obj> with <val>, if <obj> is not of <aggregate> corresponding datatype kind then an error is signaled.

The data created by make-<aggregate> and selected by <aggregate>-switch are required to conform to <my-datatype> properties. With modules it is possible not to export the datatype value nor the generic aggregate functions but only functions defined on top of them. This way one could have finer control of aggregate usage. The idea is to allow flexibility by exposing only interface functions via modules exports and that it is impossible to rebuild aggregate functions if corresponding datatype (with unique designation) is not exported.

Accessing aggregate components


  1. Unsafe access procedures must be invoked after a predicate checking data type
  2. Or safe access procedure, then a check is performed before accessing and an error is signalled if the data type is not what is expected.

However data are aggregated in order to retrieve many part of it and not only one. Solution 2 requires to perform redundant check for each accessed field and moreover the error in general is fatal to the program execution. Solution 1 alone is unsatisfactory as if an unsafe access procedure is applied to not of the correct kind data then random and unwanted behaviors may appear.

Another solution is to group together data type checking with accessing in a case analysis function (per aggregate types) :

(<aggregate>-switch <aggregate-case> <else-case>) = (lambda (<obj>) ...)

Two cases are possible: if the data <obj> is of <aggregate> corresponding datatype kind then <aggregate-case> function is called with the components of the <obj> data, else <else-case> is called with <obj>.

The idea behind my-datatype-switch function is to open an environment with bindings for corresponding aggregate components.


A data is a variant if it is one kind of a list of aggregates.

For instance, one could view the following classic types as variant:

Convenience macro for variants may be provided as in [see 2]:

(define-syntax variant-case (syntax-rules (else) ((variant-case <obj>) (error "variant-case: all case exhausted " <obj>)) ((variant-case <obj> (else <body> ...)) (begin <body> ...)) ((variant-case <obj> (<aggregate-switch> (<var> ...) <body> ...) rest ...) ((<aggregate-switch> (lambda (<var> ...) <body> ...) (lambda (<obj>) (variant-case <obj> rest ...))) <obj>))))

SRFI-9 records interface

(define-syntax define-record-type (syntax-rules () ((define-record-type typename (constructor constructor-tag ...) predicate (field-tag accessor . more) ...) (begin (define-datatype type type-info 'typename ((mutable field-tag) ...)) (define-values-with (type-constructor type-switch mutators) (create-aggregate-functions type)) (define constructor (lambda (constructor-tag ...) (type-constructor field-tag ...))) (define predicate (type-switch (lambda (field-tag ...) #t) (lambda (obj) #f))) ;; Macro call for accessors and optional modifiers (define-record-field type-switch mutators (field-tag ...) field-tag accessor . more) ...)))) (define-syntax define-record-field (syntax-rules () ((define-record-field type-switch mutators field-list field-tag accessor) (begin (define accessor (type-switch (lambda field-list field-tag) (lambda (obj) (error "Invalid type: " obj)))))) ((define-record-field type-switch mutators field-list field-tag accessor modifier) (begin (define accessor (type-switch (lambda field-list field-tag) (lambda (obj) (error "Invalid type: " obj)))) (define modifier (cadr (assoc 'field-tag mutators)))))))

Various examples

(define-datatype null-type null-type-info "NULL" ()) (define-values (make-null null-switch null-mutators) (create-aggregate-functions null-type)) (define-values (make-pair pair-switch pair-mutators) (create-aggregate-functions (make-datatype "PAIR" (first second)))) ;; With my-car, my-cdr for instance: (define (my-car obj) ((pair-switch (lambda (first second) first) error) obj)) (define (my-cdr obj) ((pair-switch (lambda (first second) second) error) obj)) (define mypair (make-pair 'one 'two)) (and (eq? 'one (my-car mypair)) (eq? 'two (my-cdr mypair))) ;; MAYBE data kind maker: either empty or containing some data. (define-values (make-empty maybe-switch maybe-mutators) (create-aggregate-functions (make-datatype "Empty" ()))) (maybe-switch <empty-case> <not-empty-case>) ;; 3d point example (define-datatype point3d-type point3d-info "3d point" ((mutable X) (mutable Y) (mutable Z))) (define-values (make-point3d point3d-switch point3d-mutators) (create-aggregate-functions point3d-type)) (define point3d-set-X! (cadr (assoc 'X point3d-mutators))) (define point3d-set-Y! (cadr (assoc 'Y point3d-mutators))) (define point3d-set-Z! (cadr (assoc 'Z point3d-mutators))) (define (point3d-length x y z) (sqrt (+ (* x x) (* y y) (* z z)))) (define (point3d-scale alpha) (lambda (x y z) (make-point3d (* alpha x) (* alpha y) (* alpha z)))) (define p3d (make-point3d 3 4 5)) ((point3d-switch point3d-length error) p3d) ;; 7.07... ((point3d-switch (point3d-scale -2) error) p3d) ;; [-6 -8 -10] (point3d-set-Y! p3d -1) (display ((point3d-switch list error) p3d)) ;; (3 -1 5) ;; Binary tree example (define-datatype bin-leaf-type bin-leaf-info "Binary tree leaf" (Data)) (define-values (make-bin-leaf bin-leaf-switch bin-leaf-mutators) (create-aggregate-functions bin-leaf-type)) (define-datatype bin-node-type bin-node-info "Binary tree node" (Data Left Right)) (define-values (make-bin-node bin-node-switch bin-node-mutators) (create-aggregate-functions bin-node-type)) ; variant-case example (define (map-tree fun bin-tree) (variant-case bin-tree (bin-node-switch (data left right) (make-bin-node (fun data) (map-tree fun left) (map-tree fun right))) (bin-leaf-switch (data) (make-bin-leaf (fun data))) (else (error "Not a bin-tree: " bin-tree)))) ;; Unforgeable aggregate with built-in assertion checking. (define-syntax create-aggregate-with-assertion (syntax-rules () ((create-aggregate-with-assertion <datatype-designation> (<field> ...) <assertion>) (let () (define-datatype type type-info <datatype-designation> (<field> ...)) (call-with-values (create-aggregate-functions type) (lambda (maker switch mutators) (values (lambda (<field> ...) (if (<assertion> <field> ...) (maker <field> ...) (error "Assertion failed: " (list <datatype-designation> <field> ...)))) switch))))))) (call-with-values (lambda () (create-aggregate-with-assertion "Interval" (left right) <)) (lambda (maker switch) (maker 4 2))) ;; error (call-with-values (lambda () (create-aggregate-with-assertion "Interval" (left right) <)) (lambda (maker switch) (maker 2 4))) ;; Ok



Disjointness issue raised and proposal:

[1] Jonathan A. Rees. "User-defined data types". Lisp Pointers. 'The Scheme of Things' (column). 1993

For variant-case to destructure records:

[2] Daniel P. Friedman, Mitchell Wand, and Christopher T. Haynes. Essentials of Programming Languages. MIT Press and McGraw-Hill, 1991.

RTD (datatype) functions:

[3] Jonathan A. Rees, Norman I. Adams IV and James R. Meehan. "The T manual". Yale University Computer Science Department. 1984.