まずはクラスの定義。
(define-class クラス名 (スーパークラス名 ...) (スロット定義 ...))クラス名とスーパークラス名はそのまんまの意味だからいいとして、スロットという見慣れない言葉の正体は、他の言語で言うところのクラスメンバのようなもの。オプション次第でインスタンス変数になったり、クラス変数になったりする。
クラスのインスタンスを作成するには
(make クラス名)とする。また、インスタンスのスロットにアクセスするには
(slot-ref インスタンス スロット名)とする。
(slot-set! インスタンス スロット名 値)
例えば、このように使う。
(define-class <animal> () (name (age :init-value 0)))スロット定義は、名前だけ指定すれば単なるインスタンス変数となる。(名前 オプション ...)の形で定義すると、初期値やらgetterやらsetterやらを指定できる。が、他の言語にあるようなアクセス権はないので、カプセル化に関しては使う側の良識に依存している。
(define an-animal (make <animal>))
(slot-set! an-animal 'name "Nanashi")
(print (slot-ref an-animal 'age))
なお、クラス名を<>で括っているのは単なる慣例であって、文法上必要なわけではない。
ちょいと継承も使ってみよう。
(define-class <fish> (<animal>) ((in-saltwater? :init-value #f)))スーパークラスのスロットも、サブクラスで追加したスロットも、同じように使える。
(define-class <bird> (<animal>) ((fly? :init-value #t :init-keyword :fly? :getter is-able-to-fly?)))
(define carp (make <fish>))
(define a-penguin (make <bird> :fly #f))
(print (slot-ref carp 'age))
(print (is-able-to-fly? a-penguin))
問題は多態性。Schemeの場合、他の多くの言語のようにメソッドがクラスに含まれるわけではなく、名前は同じだが引数のタイプや数が異なる関数の多重定義によって、多態性を実現する。ジェネリック関数と呼ばれるこのタイプの関数は
(define-method 関数名 (引数 ...) 処理内容 ...)と定義する。例えば
(define-method bark ((a <dog>)) (print "bow"))とすると、引数が<dog>クラスのインスタンスなら"bow"と吠え、<cat>クラスのインスタンスなら"mew"と鳴く、bark関数が定義される。
(define-method bark ((a <cat>)) (print "mew"))
大事なことなので2度書くが、ジェネリック関数はクラスの中にある特別な関数ではない。なので、特別に名前のみでスロットにアクセスできたりするようなことはない。
0 件のコメント:
コメントを投稿