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, sodefine-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.
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.