This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-150@nospamsrfi.schemers.org
. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI provides a specification and portable implementation of an extension of the ERR5RS record syntax of SRFI 131, where field names inserted by macro transformers are effectively renamed as if the macro transformer inserted a binding. This makes this SRFI compatible with the semantics of the record-type definitions of the R7RS as intended by its authors. In addition, field names may also be other types of Scheme datums, like numbers and strings, or SRFI 88 keyword objects.
Consider the following record-type definition in the style of SRFI 9: Defining Record Types, which was adopted and extended by the R7RS:
(define-record-type <record> (make-record field1 field2) record? (field1 record-field1) (field2 record-field2))
Both field1
and field2
are identifiers.
The two formal arguments of the constructor make-record
correspond to the two fields with the same names.
If the above record-type definition results from the output of a
syntax-rules
macro transformer, which in turn may
result in the hygienic renaming of identifiers, the question arises
how the identifiers making up the formal arguments of the
constructor are compared to the identifiers making up the field
names.
There are two reasonable answers to this question: Either the
identifiers are considered raw symbols, in which case they are equal
if and only if they would denote the same literal if quoted, or they
are considered bound identifiers in the region of the record-type
definition, in which case they are equal if and only if a binding
for one would shadow free references to the other (that is,
being bound-identifier=?
in the terminology of
the appendix
to the R4RS). For simplicity, in the sequel, we will call field
names with the former semantics unhygienic field names, and
field names with latter semantics hygienic field names.
(This terminology is meant to be free of any judgement.)
It is not so clear, however, what the answer to this question is,
or should be. SRFI 9 itself does not say much about this. The
specification uses the syntax production
rule <identifier>
instead
of <symbol>
, which suggests hygienic matching.
The sample implementation accompanying SRFI 9, on the other hand,
does unhygienic matching. It is, however, only a hack, because of
the inability of
the R5RS
to create new types.
It may be more illuminating to inspect a proper implementation of this SRFI by its author, Richard Kelsey, which is the implementation of SRFI 9 in Scheme 48. Here, field names are hygienic, hinting at the intended meaning of field names.
There is also another early proper implementation of SRFI 9, namely the one found in Racket by Mike Sperber from 2001, which also does hygienic matching.
After the R6RS, whose record
system didn't build upon SRFI
9, SRFI 99:
ERR5RS Records was proposed, which extends SRFI 9, amongst
other things, by a procedural layer. Although SRFI 99 still speaks
of field names as <identifier>
s
(versus <symbol>
s, which would have made it
clear), it is clear from the relationship with the accompanying
procedural interface that field names have to be unhygienic, making
SRFI 99 incompatible with previous implementations of SRFI 9.
During conception of the R7RS, Working Group 1 decided for inclusion of SRFI 9, but decided against inclusion of SRFI 99. Working Group 1 was also aware of the question of whether field names should be matched hygienically or not, and arguments in favor of hygienic matching were given.
The resulting document of the R7RS does not explicitly state that
field names in record-type definitions are matched hygienically.
However, the terminology wording of the R7RS gives little room for
another interpretation. (If someone interpreted the R7RS in such a
way that field names are unhygienic, by the same reason, pattern
variables in syntax-rules
would be matched
unhygienically as well — this is relevant for macros
producing macro definitions. However, all implementers and users
seem to agree that pattern variables are matched hygienically.)
Alex Shinn, chair of Working Group 1,
also confirmed
that field names are hygienic in the R7RS.
Thus, the R7RS is compatible with the original SRFI 9 implementations, but may break SRFI 99 compatibility when record-type definitions result from the output of macro transformers.
If the language defined by the R7RS is viewed in isolation, the
choice made by Working Group 1 resulted in the more powerful
language. As the R7RS has only syntax-rules
macro, the
only way for macros to generate new identifiers is through hygienic
renaming. Thus, in the R7RS, syntax-rules
macros can
produce record types with an unlimited number of fields
(making define-record-type
truly generative in a second
sense). If field names were unhygienic, the following example
wouldn't work (and would not be realisable
with syntax-rules
, meaning that the various corners of
the R7RS wouldn't have fit well together):
(define *counter* -1) (define-syntax define-record-type/identity (syntax-rules () ((_ rt-name (constructor name ...) predicate id field ...) (begin (define-record-type rt-name (%constructor %id name ...) predicate (%id id) field ...) (define (constructor . args) (set! *counter* (+ 1 *counter*)) (apply %constructor *counter* args))))))
In fact, even having SRFI 99 at one's disposal, the above problem does not have a simple solution. Using the procedural layer of SRFI 99, one would have to create a symbol for the identity field disjoint from all other field names supplied. In the absence of uninterned symbols, this seems like a deficiency of SRFI 99.
One reason why SRFI 99 is interesting is that it offers inheritance of record types. A stripped down version of SRFI 99 without the procedural layer is SRFI 131. Being forward-compatible with SRFI 99, the field names of SRFI 131 are likewise unhygienic, thus being at odds with the semantics of the R7RS.
This SRFI proposes to extend the define-record-type
of
the R7RS exactly as it is done by SRFI 131 with three exceptions so
that compatibility with the R7RS semantics is maintained:
Field names inserted by hygienic macro transformers are effectively renamed making the system hygienic and compatible with the R7RS as written. (Strictly speaking, the text of SRFI 131 also allows an interpretation of field names as identifiers, not symbols, but the fact that it says that it is forward compatible with SRFI 99 implies that hygiene is stripped from identifiers.)
Field names may be identifiers or constants (in the sense of the pattern language of 4.3.2 of the R7RS).
Apart from using the field names, one can also use the identifiers bound to the accessors by a record-type description to refer to fields in the constructors.
The rationale for these additions is as follows:
syntax-rules
-macro transformers are simply
not powerful enough to generate sufficient record-type
definitions. To give another sample, the following example by Alex Shinn
would break:
(define-syntax define-tuple-type (syntax-rules () ((define-tuple-type name make pred x-ref (defaults ...)) (deftuple name (make) pred x-ref (defaults ...) (defaults ...) ())))) (define-syntax deftuple (syntax-rules () ((deftuple name (make args ...) pred x-ref defaults (default . rest) (fields ...)) (deftuple name (make args ... tmp) pred x-ref defaults rest (fields ... (tmp tmp)))) ((deftuple name (make args ...) pred x-ref (defaults ...) () ((field-name get) ...)) (begin (define-record-type name (make-tmp args ...) pred (field-name get) ...) (define (make . o) (if (pair? o) (apply make-tmp o) (make-tmp defaults ...))) (define x-ref (let ((accessors (vector get ...))) (lambda (x i) ((vector-ref accessors i) x)))))))) ;; (make-point [first second]) returns a new point ;; (point-ref pt <0 or 1>) fetches the first or second field (define-tuple-type point make-point point? point-ref (0 0)) ;; will output (0 0) if field names are matched hygienically (let ((pt (make-point))) (write (list (point-ref pt 0) (point-ref pt 1))) (newline)) ;; will output (1 2) if field names are matched hygienically (let ((pt (make-point 1 2))) (write (list (point-ref pt 0) (point-ref pt 1))) (newline))
One purpose of this SRFI is thus to demonstrate that the power of
SRFI 131's record types can also be made available on Schemes
compatible with the hygienic matching of field names of the R7RS,
and that there is no reason for unhygienic field names (being
inferior not only when syntax-rules
are considered), except
for SRFI 99 compatibility (which, as stated above, itself broke
compatibilty with prior implementations). However, at the end of
this SRFI, it is outlined how principal compatibility with SRFI 99
can be achieved if SRFI 99 is suitably extended.
This SRFI can be viewed as a competitor to SRFI 136 by the same author for inclusion in R7RS-large. Both systems emphasize that hygienic field names are possible. The way parent record-type fields are matched in constructor declarations of child record-types, however, differs. While SRFI 136 uses position to identify fields (in the spirit of the R6RS), this SRFI 150 is more in the spirit of the syntax of SRFI 131 (and thus SRFI 99). (When string field names are silently converted to symbols when moving from the syntactic to a procedural layer even the interface of SRFI 99 continues to work with this SRFI.)
Specifying a procedural interface (possibly along the lines of SRFI 99) as an alternative syntactic interface presented in this SRFI is left to a future SRFI when there will have been an agreement on whether keyword objects and/or run-time representation (e.g. in the form of syntactic closures) of renamed identifiers will be included in a future revision of the Scheme report. However, following a suggestion Shiro Kawai, a possible procedural interface is outlined below to give implementations a hint how a future specification may look like, which should reduce the risk of mutually incompatible implementations in the future.
A Scheme system supporting keyword objects through SRFI 88 should also allow keywords as field names. Using keywords instead of identifiers, one gets back exactly the semantics of SRFI 131 (and SRFI 99).
<definition> -> <record-type definition> <record type definition> -> (define-record-type <type spec> <constructor spec> <predicate spec> <field spec> ...) <type spec> -> <type name> -> (<type name> <parent>) <constructor spec> -> #f -> <constructor name> -> (<constructor name> <field name> ...) <predicate spec> -> #f -> <predicate name> <field spec> -> (<field name> <accessor name>) -> (<field name> <accessor name> <mutator name>) <parent> -> <type name> -> #f <type name> -> <identifier> <constructor name> -> <identifier> <predicate name> -> <identifier> <accessor name> -> <identifier> <mutator name> -> <identifier> <field name> -> <identifier> -> <pattern datum>
The semantics of a record type definition is the same as in the R7RS except for the following additions:
The record-type definition macro-expands into a cluster of definitions that:
<type name>
to the record-type
descriptor for the new record-type (unless the <type
name>
is #f
);<constructor spec>
is #f
);<predicate
spec>
is #f
);A record type definition extends R7RS with the following additional options:
<parent>
datum is specified and
not #f
, then it must be an identifier bound to a
record-type descriptor that serves as the parent record-type for
the record-type being defined. (A
missing <parent>
datum is equivalent
to #f
.)
#f
is specified for the constructor or predicate,
then no constructor or predicate procedure is defined. (This is
useful when the record-type being defined will be used as an
abstract base class and has no immutable fields.)When a constructor spec is of the form (<constructor
name> <field name> ...)
:
<field name>
s can either be a
field name or accessor name declared in the
same define-record-type
form, or any of its
ancestor's field names or accessor names. Equality is defined as
follows: Two constants are considered equal if they are equal in
the sense of equal?
. An identifier is never
considered equal to a constant. Two identifiers appearing in two
different instances of define-record-type
are
considered equal if and only if they have the same lexical binding
or the two identifiers are the same (taking renaming due to
hygiene into account) and both have no lexical binding. Two
identifiers appearing in the same instance
of define-record-type
are considered equal if binding
one identifier would bind the other identifier (in other words:
both identifiers are the same after renaming due to hygiene). If
a field name <field name>
is considered equal
to an identifier naming a field name and an identifier naming an
accessor, the field name takes precedence. Field names in
children shadow field names in parents.
This section is non-normative but may become normative in a future SRFI.
For a Scheme system supporting first class identifiers and first class syntactic environments and the procedural layer of SRFI 99 it is recommended that the procedural layer of SRFI 99 is extended as described below so that the syntactic layer defined by this SRFI can be based upon it:
In the definition of make-rtd
in the procedural
layer of SRFI 99, a field specifier may take one of the following
forms:
(mutable name)
,
where name is as in 1. – 3. naming the mutable
field;(immutable name)
,
where name is as in 1. – 3. naming the
immutable field.
In the definition of rtd-constructor
in the
procedural layer of SRFI 99, fieldspecs is a vector of
objects naming fields, where each object is of one of the
following forms:
rtd-accessor
naming the
field accessor (and thus the field itself) in the style of this SRFI.
In the definitions of rtd-accessor
and rtd-mutator
, field is an object of
one the following forms:
rtd-accessor
naming the
field accessor (and thus the field itself) in the style of this SRFI.
An implementation may restrict rtd-accessor
to return always the same
accessor procedure per field in order to make the naming of field
accessors as used by the procedural layer described here
unambiguous.
The sample implementation builds on SRFI 137 to provide unique types. The syntax is implemented using SRFI 148, whose sample implementation relies on SRFI 147.
The implementation also serves as a demonstration for the succinct style of the macro system of SRFI 148 (for comparison, take a look at the source of SRFI 131 or SRFI 136).
The define-record-type
identifier exported by the
library (srfi 150)
of the sample implementation is not
bound to the same syntactic keyword,
the define-record-type
-identifier exported by the core
library (scheme base)
. This is because the base
library cannot be portably redefined.
Credit goes to all members of the Scheme Working Group 2, who participated in the discussion of record types in R7RS-large. Quite a lot of wording was copied verbatim from SRFI 131 (and SRFI 136).
Credit also goes to Alex Shinn for setting up a summary parts of the discussion.
I would also like to thank Shiro Kawai for suggesting to include the informal section of compatibility with the SRFI 99 procedural layer.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.