きくらげ観察日記

好きなことを、適当に。

ジェネリック関数を定義する

Gaucheでは、アドホックに多相を実現する手段としてジェネリック関数が用意されています。

;; 足し算
(define (add x y) (+ x y))
(add 3 2) ; => 5

;; ベクターも足し算したくなった
(define (vector-add xs ys) (vector-map + xs ys))
(vector-add #(1 2 3) #(4 5 6)) ; => #(5 7 9)

(define-method add ((x <number>) (y <number>)))

このような場合をジェネリック関数を用いて書きなおしてみましょう。

;; addのジェネリック版
(define-method generic-add ((x <number>) (y <number>))
  (+ x y))

(define-method generic-add ((xs <vector>) (ys <vector>))
  (vector-map generic-add xs ys))

(generic-add 3 4) ; => 7
(generic-add #(1 2 3) #(3 2 1)) ; => #(4 4 4)

最初にdefine-methodが呼ばれた時点で、generic-addという1つのジェネリック関数が定義されます。
2回目以降のdefine-methodでは、指定された引数と関数本体がジェネリック関数の定義に追加されます。

ジェネリック関数が呼び出された時、ジェネリック関数は与えられた引数のリストに対して、最も具体的にマッチするものを選択し、その定義の本体を実行します。例えば、<number>と<integer>に対してどちらもジェネリック関数が定義されていた場合、ジェネリック関数は以下のような振る舞いをします。

(define-method get-type ((x <number>)) "number")
(define-method get-type ((x <integer>)) "integer")

(get-type 3.0) ; => "number"
(get-type 3)   ; => "integer"

また、ジェネリック関数本体の中ではnext-method関数を呼ぶことによって自身よりも1段階一般的なジェネリック関数のメソッドを呼び出すことができます。

(define-method get-type ((x <number>)) "number")
(define-method get-type ((x <real>)) "real")
(define-method get-type ((x <integer>))
  (next-method x)) ; 1段階一般的なメソッドを呼ぶようにした

(get-type 3+i) ; => "number"
(get-type 3.0) ; => "real"
(get-type 3)   ; => "real"

<number>は<real>の親、<real>は<integer>の親なので、最後のget-typeでは<integer>の親、<real>のメソッドが呼び出されます。