On Sat, 13 Nov 2004, Andre van Tonder wrote:
> I am considering extending the syntax to expose monomorphic predicates and
> accessors in addition to the current polymorphic versions.
> (define-record-type point (make-point x y) (point? just-point?)
> (x (point.x just-point.x) (point.x-set! just-point.x-set!))
> (y (point.y just-point.y) (point.y-set! just-point.y-set!)))
Sorry for responding to myself, but I think the heaviness of this
syntax may be a sign that the way subtyping currently works might
not be the best, and that one can perhaps improve on it by orthogonalizing
the mechanism for the definition of new types and the mechanism for
polymorphism.
Currently, the definition
(define-record-type point (make-point x y) point? ......)
is in fact doing two unrelated things. It introduces a new disjoint type,
instances of which can be constructed using (make-point ...), but then
goes on to introduce a polymorphic predicate and polymorphic accessors
that are not specific to that type. For example, it is impossible to test
whether a value has been constructed using the constructor (make-point ...)
as opposed to some arbitrary future subtype of point. In other words,
looking at the polymorphic predicate and the accessors, it appears as if
the 'point' definition is declaring a whole class of types (in the sense
of Haskell), so what is the constructor doing there?
Also, because the predicate point? has to be generic even in the absence
of subtypes (unless we do a whole-program analysis), we lose performance
even if we do not ever want to use the point type polymorphically. And
since we have replaced SRFI-9 monomorphic records, we cannot use the
latter for these types and SRFI-57 polymorphic records for some other
types in the same program.
To overcome these objections, I propose the following:
- Essentially keep the SRFI-9 semantics for define-record-type
(with a small modification below). All types introduced
this way are disjoint and there is *no* subtyping.
- Farm off the polymorphism to a new form, define-record-class
(or perhaps define-record-interface, according to taste).
So we could, for example, define a class of types conforming to a <point
interface as
(define-record-class <point <point? (x <point.x) (y <point.y))
where <point? and <point.x, <point.y are polymorphic, and there is
*no* constructor declared. Here <point? can be read as "conforms to a
subinterface of <point". Concrete types can then, if desired, be defined
as belonging to a particular class via
(define-record-type (point <point) (make-point x y) point?
(x point.x)
(y point.y))
The modification to SRFI-9 is in the type clause, which declares point as
conforming to the <point interface. The procedures point?, point.x and
point.y are monomorphic and can be implemented more efficiently than would
have been possible for polymorphic versions.
We can then define, for example
(define-record-type (color-point <point) (make-color-point x y hue)
(x color-point.x)
(y color-point.y)
(hue color-point. hue))
The types point and color-point would be disjoint, in particular
(point? (make-color-point 1 2 3)) ==> #f
(point.x (make-color-point 1 2 3)) ==> error
but color-point conforms to the <point? interface
(<point? (make-color-point 1 2 3)) ==> #t
(<point.x (make-color-point 1 2 3)) ==> 1
Unless there are strong objections, I will submit for review an
implementation that uses SRFI-9 directly.
Andre