Title

Hygienic ERR5RS Record Syntax (reduced)

Author

Marc Nieper-Wißkirchen

Status

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.

Abstract

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.

Rationale

The problem of hygiene and record-type field names

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.

The solution proposed by this SRFI

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:

The rationale for these additions is as follows:

Hygiene
As has been argued above, without hygiene, 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))
General field names
As child record-types in this SRFI (as in SRFI 131) refer in their constructors to fields of their parent record-type by field names (but see below) and the matching of these field names must be hygienic as well, the identifier naming the field must either be unbound both in the scope of the definition of the parent and the child record-type or must share the same binding (e.g. as auxiliary syntax) in both scopes. As this may prove to be difficult to ensure when inheritance is to happen across library boundaries, field names (of fields that are supposed to be part of a public interface) may also be constants like strings instead of identifiers. As strings are not being changed due to hygiene, all that is feasible with SRFI 131 is also feasible with this SRFI.
Accessors identifying fields
When one needs to stick to hygienic field names (that is identifiers as field names) but has to cope with the problems mentioned above about matching the field names in child record-types, one may also use the identifiers bound to the accessors of a record-type definition to refer to field names. As these identifiers are always bound to something meaningful and name clashes have to be avoided anyway, matching across library boundaries using the identifiers bound to the accessors works without any of the problems mentioned above. (If an identifier names both a field and an accessor, the matching of the identifier as a field name takes precedence.)

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.

Specification

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).

Syntax

This SRFI extends 7.1.6 of R7RS-small as follows:
 <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>

Semantics

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:

A record type definition extends R7RS with the following additional options:

When a constructor spec is of the form (<constructor name> <field name> ...):

Informative Section

This section is non-normative but may become normative in a future SRFI.

Compatibility to the SRFI 99 Procedural Layer

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:

Implementation

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.

Acknowledgements

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.

Copyright

Copyright (C) Marc Nieper-Wißkirchen (2017). All Rights Reserved.

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.


Editor: Arthur A. Gleckler