Less verbose type definition form (was: [R6RS] Records comments)

Marc Feeley feeley
Fri Jul 15 10:05:48 EDT 2005


Here is my proposal for a "less verbose" type definition form.

Grammar of "basic" type definition form
---------------------------------------

<type-definition> ::=  ( define-type <type-name> <type-attrib-or- 
field-def>* )

<type-name> ::=  <identifier>

<type-attrib-or-field-def> ::=  <type-attribute>  |  <field-definition>

<type-attribute>
   ::=  <extends-attribute>
     |  <extensible-attribute>
     |  <id-attribute>
     |  <constructor-attribute>
     |  <predicate-attribute>

<extends-attribute>
   ::=  extends: <type-name>
     |  extends: #f

<extensible-attribute>
   ::=  extensible: #t
     |  extensible: #f

<id-attribute>
   ::=  id: <globally-unique-type-name>
     |  id: #f

<globally-unique-type-name> ::=  <identifier>

<constructor-attribute>
   ::=  constructor: <constructor-name>

<constructor-name> ::=  <identifier>

<predicate-attribute>
   ::=  predicate: <predicate-name>

<predicate-name> ::=  <identifier>

<field-definition>
   ::=  <field-name>
     |  ( <field-name>                             <field-attribute>* )
     |  ( <field-name> <getter-name>               <field-attribute>* )
     |  ( <field-name> <getter-name> <setter-name> <field-attribute>* )

<field-name> ::=  <identifier> not ending with a colon
<getter-name> ::=  <identifier>
<setter-name> ::=  <identifier>

<field-attribute>
   ::=  mutable: #t
     |  mutable: #f


In the following explanation, T is the name of the type being defined  
(the <type-name> identifier immediately after "define-type"), F1 is  
the <field-name> of this type definition's first <field-definition>,  
F2 is the name of the second, etc.


Defaults for type attributes
----------------------------

At most one <extends-attribute> is allowed in a <type-definition>.   
When no <extends-attribute> appears in the <type-definition>, the  
following default is used

   extends: #f

At most one <extensible-attribute> is allowed in a <type- 
definition>.  When no <extensible-attribute> appears in the <type- 
definition>, the following default is used

   extensible: #f

At most one <id-attribute> is allowed in a <type-definition>.  When  
no <id-attribute> appears in the <type-definition>, the following  
default is used

   id: #f

At most one <constructor-attribute> is allowed in a <type- 
definition>.  When no <constructor-attribute> appears in the <type- 
definition>, the following default is used

   constructor: make-T

At most one <predicate-attribute> is allowed in a <type-definition>.   
When no <predicate-attribute> appears in the <type-definition>, the  
following default is used

   predicate: T?


Semantics
---------

The <extends-attribute> indicates the type inheritance relationship.

   extends: <type-name>     T is derived from <type-name>
                            which must be extensible.
                            T inherits the fields defined in
                            <type-name>.  Instances of T
                            are also instances of <type-name>.

   extends: #f              T is a base record type.

The <extensible-attribute> indicates whether or not T is extensible.

   extensible: #t           T is extensible.

   extensible: #f           T is not extensible.

The <id-attribute> indicates the identity of the type.  The identity  
is used by the predicate to test if an object is an instance of T.

   id: <globally-unique-type-name>  T's identity is the identifier
                                    supplied.

   id: #f                           T's identity is regenerated
                                    each time the type
                                    definition is evaluated
                                    (i.e. the type is generative).

The <constructor-attribute> indicates the name of the constructor  
procedure.

   constructor: <constructor-name>

The constructor procedure can be viewed as a lambda expression of the  
form

   (lambda (I1 I2... F1 F2...) <body>)

where I1, I2... correspond to the field names of the parent type, if  
T is a subtype.

The <predicate-attribute> indicates the name of the predicate procedure.

   predicate: <predicate-name>

The fundamental <field-definition> forms are

   ( F G   )  immutable field named F whose getter procedure
              is named G

   ( F G S )  mutable field named F whose getter procedure
              is named G and  whose setter procedure is named S

The other field definition forms can be explained in terms of the  
fundamental field definition forms

   F                     = ( F T-F T-F-set! )

   ( F )                 = ( F T-F T-F-set! )
   ( F mutable: #t )     = ( F T-F T-F-set! )
   ( F mutable: #f )     = ( F T-F          )

   ( F G )               = ( F G            )
   ( F G mutable: #t )   = *error*
   ( F G mutable: #f )   = ( F G            )

   ( F G S )             = ( F G   S        )
   ( F G S mutable: #t ) = ( F G   S        )
   ( F G S mutable: #f ) = *error*

Each field is initialized from the constructor's formal parameter of  
the same name.


Examples
--------

All of the following type definitions for a "2D point" type are  
equivalent

   (define-type point x y)

   (define-type point
     constructor: make-point
     x
     y)

   (define-type point
     predicate: point?
     (x mutable: #t)
     (y mutable: #t))

   (define-type point
     constructor: make-point
     predicate: point?
     (x point-x point-x-set!)
     (y point-y point-y-set!))

Here is the definition of an extensible 2D point and a non-extensible  
3D point

   (define-type point2D
     extensible: #t
     x
     y)

   (define-type point3D
     extends: point2D
     z)


Extensions
----------

The basic type definition form specified above could be extended to  
allow arbitrary field initialization.  One approach is to allow  
specifying the parameters of the constructor by extending the  
<constructor-attribute> category

   <constructor-attribute>
     ::=  constructor: <constructor-name>
       |  constructor: ( <constructor-name> <variable>* )

and to add a field initialization form to <field-attribute>

   <field-attribute>
     ::=  mutable: #t
       |  mutable: #f
       |  init: <expression>

The scope of the constructor's formal parameters includes the field  
initialization expressions (i.e. the <expression> in "init:  
<expression>").  If not specified the field F is initialized from the  
formal parameter of the same name.  It is also reasonable to specify  
that the <constructor-attribute>

   constructor: C

is equivalent to

   constructor: ( C F1 F2... )

When inheritance is used, it is not clear how the field  
initialization expressions are interpreted.  For instance in

   (define-type point2D
     extensible: #t
     constructor: (make-point2D x)
     x
     (y init: (* x x)))

   (define-type point3D extends: point2D
     constructor: (make-point3D z)
     z)

how are the fields x and y initialized when make-point3D is called?

I think that the parent type's formal parameters should implicitly be  
prefixed to the constructor's parameters.  So in the example above  
make-point3D would take two parameters: x (inherited from point2D)  
and z.  This means that the names of the formal parameters must be  
distinct from those of the parent's constructor.  This approach  
allows things like

   (define-type point2D
     extensible: #t
     constructor: (make-point2D x)
     x
     (y init: (* x x)))

   (define-type point3D extends: point2D
     (z init: (- x z))) ; note: accessing the parent constructor's  
parameter x
                        ; y is not accessible

An instance initializer form could also be added as a <type-attribute>.

   <type-attribute>
     ::=  <extends-attribute>
       |  <extensible-attribute>
       |  <id-attribute>
       |  <constructor-attribute>
       |  <predicate-attribute>
       |  <init-attribute>

   <init-attribute>
     ::=  init: (lambda (<self-name>) <body>)

   <self-name> ::=  <identifier>

After the record instance is allocated and the field initialization  
forms have been evaluated, the <init-attribute> is called with the  
record instance as its single parameter (after calling the <init- 
attribute>(s) of the parent(s) of course).

I'm a bit uncomfortable with this because immutable fields can't be  
initialized using the instance initializer form.

In fact, I would be content with the basic type definition form.  If  
you need to initialize fields specially, just define your own  
constructor procedure (outside of the type definition form).  For  
example:

   (define-type point2D
     extensible: #t
     constructor: make-plain-point2D
     x
     y)

   (define (make-point2D x)
     (make-plain-point2D x (* x x)))

   (define-type point3D extends: point2D
     constructor: make-plain-point3D
     z)

   (define (make-point3D x z)
     (make-plain-point3D x (* x x) (- x z)))


Anyway, with these extensions, this proposal provides the same  
features as the "featureful syntactic layer" of the Clinger/Dybvig/ 
Sperber proposal.  However, it (in my view)

1) has a syntax that is more lightweight in the common
    case, i.e. locally used non-extensible small records

2) has a syntax that is easier to parse for humans (not based
    on positional arguments)

3) has a syntax that is more extensible (for adding new type
    attributes or field attributes)

Marc



More information about the R6RS mailing list