[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

opaque type proposal



            Opaque types / record structures for R^4 Scheme

                     Draft #3, J. Rees, 26 May 1988

Norman Adams's message of 8 July 1987 to RRRS-Authors proposing record
types for Scheme met with a deafening silence.  Here is my reiteration
of his proposal.  This also is a counter-proposal to the
INJECT/PROJECT/IN?  opaque type facility that Will and/or I proposed a
while back.

The proposal consists of the following 5 procedures.  MAKE-RECORD-TYPE
creates a new "record type" object, and the other 4 procedures are
accessors for record types that return procedures.  The type of the
value returned for each procedure is given after a ":".

  (MAKE-RECORD-TYPE type-id field-names) : record-type
     where type-id is a symbol, and field-names is a list of symbols
  (RECORD-CONSTRUCTOR record-type)       : (object X ...) -> record
  (RECORD-PREDICATE record-type)         : object -> boolean
  (RECORD-ACCESSOR record-type name)     : record -> object
  (RECORD-UPDATER record-type name)	 : record X object -> unspecified

Example:

  (define yow-record-type (make-record-type 'yow '(a b c)))

  (define make-yow
    (let ((construct-yow (record-constructor yow-record-type)))
      (lambda (a-val other-val)
        (let ((b-val (compute-b other-val))
	      (c-val (compute-c a-val b-val other-val)))
          (construct-yow a-val b-val c-val)))))

  (define yow? (record-predicate yow-record-type))
  (define yow-a (record-accessor yow-record-type 'a))
  (define yow-b (record-accessor yow-record-type 'b))
  (define yow-c (record-accessor yow-record-type 'c))
  (define set-yow-c! (record-updater yow-record-type 'c))

  (yow? (make-yow 2 17))  =>  #t
  (yow-a (make-yow 2 17))  =>  2

It is an error to call an accessor or updater for a record type R on an
object that wasn't created by R's constructor.  MAKE-RECORD-TYPE is a
side effect, like CONS.  Every new record type is disjoint from every
old one and from other Scheme types such as pairs, vectors, and
procedures.  In particular, records must answer false to existing type
predicates, including PAIR?, VECTOR?, and PROCEDURE?.  Thus this
mechanism must be added primitively to Scheme.

The type-id argument to MAKE-RECORD-TYPE has no semantic content.  It is
solely for the use of the printer and related routines, to make records
easier to identify during debugging.  The field names are used only for
coordination between MAKE-RECORD-TYPE and RECORD-ACCESSOR/UPDATER.

-----

Notes: 

The use of the word "type" for an object that describes a type is
somewhat inappropriate, and would raise the hackles of people who
believe they know what types are.  The objects might better be called
"record type descriptors" even though that's a little long; the
constructor would then be MAKE-RECORD-TYPE-DESCRIPTOR.

This is essentially T's record package (MAKE-STYPE), except that the
constructor takes as arguments initial values for all the fields,
rather than for none of them.

No general record-type-defining macro is proposed.  Defining a good one
is difficult or impossible; I have tried many times and failed to come
up with something that's both elegant and flexible enough.  Once we have
macros, people can roll their own, so we shouldn't clutter the language
at this point.

Initial value arguments to constructors could be passed by keyword, as
in Common Lisp, but they're positional here for consistency with the
rest of Scheme, and for efficiency.

There is intentionally no way to go from a record to its record-type.
Clearly implementations can (and for debugging, would) provide such a
procedure, but it would have the same abstraction-breaking ability
that something like PROCEDURE-CODE have.

Question: should there be a (RECORD-COPIER record-type) as in CL?
  - Will Clinger says no; a copier for a particular record type
    can be written pretty easily.
  - Richard Kelsey says yes, for performance and convenience.

Issues purposely avoided: subtyping (a.k.a. inheritance); print methods
(a.k.a. object-oriented programming); integration with modules and/or
environments (a.k.a. Pascal WITH).  While these are all probably
desirable, they open many worm cans, and we should try to prevent the
consideration of such questions to slow the adoption of this proposal.

-----

Here is a sample implementation.  It is not minimal, but is instead
intended to be somewhat realistic.

A real implementation would require that records not satisfy VECTOR? (or
any other existing Scheme type predicate).

(define (make-record-type type-id field-names)

  (define (constructor . field-values)
    (if (= (length field-values) (length field-names))
	(apply vector the-record-type field-values)
	(error "wrong number of arguments to record constructor"
	       type-id field-values)))

  (define (predicate obj)
    (and (vector? obj)
	 (> (vector-length obj) 0)
	 (eq? (vector-ref obj 0) the-record-type)))

  (define (accessor name)
    (let ((i (field-index name)))
      (lambda (record)
	(if (predicate record)
	    (vector-ref record i)
	    (error "not a record" record name)))))

  (define (updater name)
    (let ((i (field-index name)))
      (lambda (record new-value)
	(if (predicate record)
	    (vector-set! record i new-value)
	    (error "not a record" record name)))))

  (define (field-index name)
    (let loop ((l field-names) (i 1))
      (if (null? l)
	  (error "bad field name" name)
	  (if (eq? name (car l))
	      i
	      (loop (cdr l) (+ i 1))))))

  (define the-record-type
    (lambda (request)
      (case request
	((constructor) constructor)
	((predicate) predicate)
	((accessor) accessor)
	((updater) updater))))

  the-record-type)

(define (record-constructor r-t) (r-t 'constructor))
(define (record-predicate r-t) (r-t 'predicate))
(define (record-accessor r-t field-name) ((r-t 'accessor) field-name))
(define (record-updater r-t field-name) ((r-t 'updater) field-name))