跳至内容

Scheme 编程/面向对象

来自 Wikibooks,开放的世界,开放的书籍

Scheme 有许多面向对象系统。本章将介绍不同的对象系统。

R7RS 中的一个对象库是 Virgo。要导入它,请输入以下内容

> (import (virgo user))

许多实现,例如 Chibi、Gauche、Guile 和 Chicken,都有自己的 CLOS 类系统,它们在语法上可能大不相同,但在语义上非常接近。Virgo 被选择用于可移植性,但这些概念将适用于所有实现。

定义一个类

[编辑 | 编辑源代码]

这是定义类的格式

> (define-class <point> ()
    (x 'init-value 0.0)
    (y 'init-value 0.0))

构造一个对象

[编辑 | 编辑源代码]

只需使用 make 创建一个新对象,并初始化其值。

> (define pt (make <point>))

获取和设置

[编辑 | 编辑源代码]

用于获取和设置值的程序分别是 slot-ref 和 slot-set!。

> (slot-ref pt 'x)
0.0
> (slot-set! pt 'x 100.0)
> (slot-ref pt 'x)
100.0

与 Java 或 C# 不同,Virgo 有一个 CLOS 类对象系统,这意味着方法不属于单个类。相反,我们定义一个泛型,然后为该泛型分配方法以处理不同的类。这是一个例子

> (define-generic distance)
> (define-method distance ((p <point>))
    (sqrt (+ (square (slot-ref p 'x)) (square (slot-ref p 'y)))))
> (distance pt)
100.0

方法也可以与多个类一起使用。为了举一个无稽之谈的例子

> (define-generic append-anything)
> (define-method append-anything ((p <point>) (s <string>))
    (string-append (number->string (slot-ref p 'x)) s))
> (append-anything pt "Hello")
"100.0Hello"

Virgo 还具有继承功能。这是一个例子

> (define-class <3point> (<point>)
    (z 'init-value 0.0))
> (define pt3 (make <3point>))
> (slot-ref pt3 'x)
0.0
> (slot-ref pt3 'z)
0.0

Prometheus

[编辑 | 编辑源代码]

与 CLOS 类系统不同,Prometheus 使用基于原型的对象而不是类。此外,方法与 Java 或 C# 中类似,绑定到对象。此外,对象不是不交集类型,而是将传递给它的第一个参数解释为方法名称的程序。

如果您使用的是 R7RS 实现并且已安装 Prometheus 库,则可以使用以下命令加载库

> (import (prometheus user))

定义一个对象

[编辑 | 编辑源代码]

定义对象与 CLOS 类系统没有太大区别。请注意,对象必须从另一个对象继承,除非它是根对象。如果您不希望它从其他任何东西继承,则该对象应该从 *the-root-object* 继承。以我们的点示例为例

> (define-object <point> (*the-root-object*)
    (x set-x! 0.0)
    (y set-y! 0.0))

克隆一个对象

[编辑 | 编辑源代码]

define-object 语法只是语法糖,您并不总是需要使用 define-object 创建新实例。相反,您可以像这样克隆一个对象,回想一下对象只是程序

> (define pt (<point> 'clone))

获取和设置

[编辑 | 编辑源代码]

同样,获取和设置只是向对象传递不同的符号。

> (pt 'x)
0.0
> (pt 'set-x! 100.0)
> (pt 'x)
100.0

当然,对象与它们相关联的方法。方法是至少有两个参数的闭包:self,传递给闭包的对象,以及 resend,它调用父对象的行為。对此的语法糖是 define-method

> (define-method (<point> 'distance self resend)
    (sqrt (+ (square (self 'x)) (square (self 'y)))))
> (pt 'distance)
100.0

也可以在使用 define-object 语法糖定义对象时为对象定义方法。

> (define-object <3point> (<point>)
    (z set-z! 0.0)

    ((distance self resend)
     (sqrt (+ (square (self 'x))
              (square (self 'y))
              (square (self 'z))))))
> (define pt3 (<3point> 'clone))
> (pt3 'set-x! 3.0)
> (pt3 'set-z! 4.0)
> (pt3 'distance)
5.0

"YASOS" 或 "Yet Another Scheme Object System" 是 Scheme 的一个特别简单的对象系统。YASOS 与 T(Scheme 的一种旧方言)的对象系统非常相似。让我们来看看它的特性。

如果您使用的是 R7RS 实现并且已安装 YASOS 库,则可以使用以下命令加载库

> (import (yasos))

如果您已安装并加载了 SLIB,您也可以执行此操作

> (require 'yasos)

谓词和操作

[编辑 | 编辑源代码]

与 CLOS 类系统相比,YASOS 可能感觉有点反常。首先,我们声明操作和谓词,然后我们创建对象。让我们继续使用点示例进行比较

> (define-predicate point?)
> (define-operation (get-x p))
> (define-operation (get-y p))
> (define-operation (set-x! p value))
> (define-operation (set-y! p value))
> (define-operation (distance p))

现在我们已经定义了点的操作,我们将定义一个对象,该对象在其方法中处理这些操作。对象的 method 语法与 Prometheus 类似。我们不会使用内置的构造函数语法,而是会定义一个返回新构造对象的程序。

> (define (make-point x y)
    (object
     ((point? self) #t)
     ((get-x self) x)
     ((get-y self) y)
     ((set-x! self value) (set! x value))
     ((set-y! self value) (set! y value))
     ((distance self)
      (sqrt (+ (square x) (square y))))))
> (define pt (make-point 0.0 0.0))
> (get-x pt)
0.0
> (set-x! pt 100.0)
> (get-x pt)
100.0
> (set-y! pt 100.0)
> (distance pt)
141.4213562373095

这种设计还意味着必须在构造对象时定义 method,并且不能在之后添加。

YASOS 使用 object-with-ancestors 语法来允许继承,这将赋予对象“祖先”或“父”对象的特征。

> (define-predicate point3?)
> (define-operation (get-z p))
> (define-operation (set-z! p value))
> (define (make-point3 x y z)
    (object-with-ancestors ((p (make-point x y)))
     ((point3? self) #t)
     ((get-z self) z)
     ((set-z! self value) (set! z value))
     ((distance self)
      (sqrt (+ (square x) (square y) (square z))))))
> (define pt3 (make-point3 1.0 2.0 3.0))
> (get-x pt3)
1.0
> (get-z pt3)
3.0
> (distance pt3)
3.7416573867739413
华夏公益教科书