跳至內容

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
華夏公益教科書