我正在考虑Scheme的类似Dylan的对象系统的实现。(最好用于完全可移植的R7RS方案。)在Dylan中有一个密封类的概念:不能从定义类的模块之外的密封类继承。
将R7RS库视为模块似乎很自然。然而,R7RS方案中的库是静态的:在运行时不会保留任何关于它们的信息。从库导入绑定后,它似乎与所有其他绑定都不可区分。
这是sealed
实现的一个问题。假设一个类是由某种define-class
形式创建的。这种形式有效地扩展为类似的东西
(define <new-class> (make <class> ...))
然后,<new-class>
绑定可以从创建它的库中导出,并导入到其他库中(可能使用不同的名称)。假设我们在库a中创建一个密封的<new-class>
并将其导入库B。从B调用的make
如何判断它是否可以创建<new-class>
的子体?如何允许从A调用的make
无条件地创建<new-class>
的子类?
(让我们忽略这种方法的一个缺点:R7RS允许多次加载<new-class>
库,这有效地创建了几个不同的<new-class>
类对象。我真的不知道如何解决这个问题。)
一个想法是将所有类定义封装到一个表单中:
(define-library (A)
(import (dylan))
(export <new-class>)
(begin
(dylan-module
(define-class <new-class> <object>
... ) ) ) )
在dylan-module
中定义的密封类可以从继承,但一旦表单结束,它们就真正密封了。然而,我只想出了一种实现这一点的方法:
(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) ) ) ) )
它是这样使用的:
(define-library (A)
(import (scheme base)
(dylan) )
(export <new-class>)
(begin
(dylan-module define-class
(define-class <new-class> <object>
... ) ) ) )
它的愚蠢之处在于:
用户需要拼写出
define-class
来正确地重新定义它(在Dylan中,泛型函数也可以密封,因此define-generic
将在其后出现);泛型
make
无法以安全的方式创建密封类,应始终使用define-class
宏(或其他特殊情况)。
在我看来,您不应该尝试将R6RS/R7RS库重新用作类,而是直接在Scheme中构建自己的类。库旨在在编译时提供命名空间控制,而不是在运行时执行任何操作。