Simulate a first-class library in R7RS Scheme

388 views Asked by At

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.

1

There are 1 answers

0
John Cowan On

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.