返回介绍

5.4 在 Scheme 中嵌入类

发布于 2025-02-20 00:17:08 字数 3702 浏览 0 评论 0 收藏 0

本节我们使用宏在 Scheme 中嵌入类。

5.4.1 类的宏

我们来定义 CLASS 语法抽象,它负责创建类:

(defmac (CLASS ([field f init] ...)
               ([method m params body] ...))
     #:keywords  field method
     #:captures self
     (let ([methods (list (cons 'm (λ (self)
                                     (λ params body))) ...)])
       (letrec
           ([class
                (λ (msg . vals)
                  (case msg
                    [(create)
                     (make-obj class
                               (make-hash (list (cons 'f init) ...)))]
                    [(read)
                     (dict-ref (obj-values (first vals)) (second vals))]
                    [(write)
                     (dict-set! (obj-values (first vals)) (second vals) (third vals))]
                    [(invoke)
                     (if (assoc (second vals) methods)
                         (apply ((cdr (assoc (second vals) methods)) (first vals)) (cddr vals))
                         (error "message not understood"))]))])
         class)))

5.4.2 辅助语法

我们需要引入新的语法定义,以方便地调用方法( -> ),还需要引入类似的语法,来访问当前对象的字段( ?! )。

(defmac (-> o m arg ...)
  (let ((obj o))
    ((obj-class obj) 'invoke obj 'm arg ...)))

(defmac (? fd) #:captures self
  ((obj-class self) 'read self 'fd))

(defmac (! fd v) #:captures self
  ((obj-class self) 'write self 'fd v))

还可以定义辅助函数来创建新的实例:

(define (new c)
  (c 'create))

这个简单的函数在概念上非常重要:它有助于隐藏类在内部作为函数实现的事实,还隐藏了用于请求类创建实例的符号。

5.4.3 例子

来看类的例子:

(define Point
 (CLASS ([field x 0])
        ([method x? () (? x)]
         [method x! (new-x) (! x new-x)]
         [method move (n) (-> self x! (+ (-> self x?) n))])))

(define p1 (new Point))
(define p2 (new Point))


> (-> p1 move 10)
> (-> p1 x?)
10
> (-> p2 x?)
0

5.4.4 强封装

关于字段访问,我们做了个重要的设计决定:字段访问器 ?! 只能作用于 self !即,在我们的语言中不可能访问另一个对象的字段。这被称为具有 强封装 (Strong Encapsulation)对象的语言。Smalltalk 就是这样(访问另一个对象的字段实际上是发送消息,因此可以由接收方对象来控制)。Java 不是:可以访问任何对象的字段(如果可见性(visibility) 允许的话)。我们的 语法 根本不允许访问外部字段。

这样设计的另一个结果是,字段访问只能出现在方法体 :因为接收对象总是 self ,所以 self 必须已定义。比如说,试试在对象之外用 ? 读取字段:

> (? f)
self: undefined;
 cannot reference undefined identifier

更好的做法是,上述程序会产生错误,表明 ? 未定义。要做到这一点,我们简单地将 ?? 定义为 局部 语法形式,只在方法体的内被定义,而不是全局范围内有定义。只要将这些字段访问形式的定义从全局移动到 local 作用域内, local 放在方法定义内:

(defmac (CLASS ([field f init] ...)
               ([method m params body] ...))
     #:keywords field method
     #:captures self ? !
     (let ([methods
            (local [(defmac (? fd) #:captures self
                      ((obj-class self) 'read self 'fd))
                    (defmac (! fd v) #:captures self
                      ((obj-class self) 'write self 'fd v))]
              (list (cons 'm (λ (self)
                               (λ params body))) ...))])
                (letrec
                   ([class (λ (msg . vals) ....)]))))

在方法列表定义的局部作用域内定义语法形式 ?! ,确保了它们可以在方法体内可用,但在其他地方不可用。

现在,字段访问器方法之外没有定义:

> (? f)
?: undefined;
 cannot reference undefined identifier

后文统一使用这种局部的方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文