2

I'm thinking of an implementation of Dylan-like object system for Scheme. (Preferably for a fully portable R7RS Scheme.) In Dylan there is a concept of sealed classes: one cannot inherit from a sealed class outside of the module where the class is defined.

It seems natural to treat R7RS libraries as modules. However, libraries in R7RS Scheme are static: nothing about them is retained at run-time. After a binding is imported from a library, it seems to be undistinguishable from all other bindings.

Well, this is a problem for the sealed implementation. Suppose a class is created by some define-class form. This form effectively expands into something like

(define <new-class> (make <class> ...))

Then the <new-class> binding can be exported from a library where it was created, and imported into some other library (possibly under a different name). Let's say we create a sealed <new-class> in library A and import it to library B. How can make invoked from B tell whether it can create descendants of <new-class> or not? And how can make invoked from A be allowed to create subclasses of <new-class> unconditionally?

(Let's ignore a shortcoming of such approach: R7RS permits to load the <new-class> library several times, which effectively creates several distinct <new-class> class objects. I don't really know how to solve this.)


One idea was to enclose all class definitions into a form:

(define-library (A)
  (import (dylan))
  (export <new-class>)
  (begin
    (dylan-module
      (define-class <new-class> <object>
        ...  ) ) ) )

Sealed classes defined inside the dylan-module can be inherited from, but they are really sealed once the form is over. However, I came up with only one way to implement this:

(define-syntax dylan-module
  (syntax-rules ()
    ((dylan-module %define-class body1 body2 ...)
     (begin
       ;; We will gather here all classes that are defined
       ;; inside the dylan-module form.
       (define to-be-sealed (list))

       ;; Locally redefine define-class to define a class
       ;; and add it to the list.
       ;;
       ;; It is necessary to pass %define-class explicitly
       ;; due to hygienic renaming: we want to allow %define-class
       ;; to be used inside of the body of the dylan-module form,
       ;; so we need to use a name from the environment where the
       ;; body is actually written.
       (let-syntax ((%define-class
                      (syntax-rules ()
                        ((%define-class name other (... ...))
                         (begin
                           (define-class name other (... ...))
                           (set! to-be-sealed
                                 (cons name to-be-sealed) ) ) ) ) ))
         body1 body2 ... )

       ;; The `seal` function is defined elsewhere.
       ;; `make` is allowed to subclass the sealed classes
       ;; until they are actually sealed by `seal`.
       (for-each seal to-be-sealed) ) ) ) )

And it is used like this:

(define-library (A)
  (import (scheme base)
          (dylan) )
  (export <new-class>)
  (begin
    (dylan-module define-class
      (define-class <new-class> <object>
        ...  ) ) ) )

The dumb things about it are:

  • the user is required to spell out define-class to properly redefine it (in Dylan generic functions can be sealed too, so define-generic will come after that);

  • generic make can't create a sealed class in a safe way, define-class macro (or some other special case) should always be used.

Chris
  • 148
  • 1
  • 9

1 Answers1

0

In my view, you should not attempt to repurpose R6RS/R7RS libraries as classes, but build your own classes directly in Scheme. Libraries are meant to provide namespace control at compile time, not to do anything at run time.

John Cowan
  • 1,497
  • 12
  • 13