暇だったので Gauche のマニュアルを読んでいると、めっちゃ面白い事が分かった。特に、Smalltalk みたいなちゃんとしたオブジェクトがある所。今まで lisp はどれも emacs lisp と似たようなもんだと思ってたのでびっくり。そこで、http://squab.no-ip.com:8080/wiki/835 を基に、Gauche のオブジェクトを覗いてみた。
Bank Account
まず Gauche のクラスを使って預金口座
gosh> (define-class <bank-account> () (dollars)) <bank-account> gosh> (define my-account (make <bank-account>)) my-account gosh> (d my-account) #<<bank-account> 0x103db7b8> is an instance of class <bank-account> slots: dollars : #<unbound>
要素に値を代入するには slot-set! 参照するには slot-ref を使う。
gosh> (slot-set! my-account 'dollars 100) #<undef> gosh> (slot-ref my-account 'dollars) 100
アクセッサと預金(deposit)と引き出し(withdraw) を作ってみる。関数は、例えば (dollars my-account) なら読み込み、(dollars my-account 100) なら書き出しという風に引数の数に応じて別の処理を割り当てる事が出来る。
(define-method dollars (self) (slot-ref self 'dollars)) (define-method dollars (self x) (slot-set! self 'dollars x)) (define-method deposit (self x) (dollars self (+ (dollars self) x))) (define-method withdraw (self x) (dollars self (max 0 (- (dollars self) x))))
gosh> (define my-account (make <bank-account>)) my-account gosh> (dollars my-account 200) #<undef> gosh> (deposit my-account 50) #<undef> gosh> (dollars my-account) 250 gosh> (withdraw my-account 100) #<undef> gosh> (dollars my-account) 150 gosh> (withdraw my-account 200) #<undef> gosh> (dollars my-account) 0
次に、
(define-class <stock-account> (<bank-account>) (num-shares price-per-share)) (define-method num-shares (self) (slot-ref self 'num-shares)) (define-method num-shares (self x) (slot-set! self 'num-shares x)) (define-method price-per-share (self) (slot-ref self 'price-per-share)) (define-method price-per-share (self x) (slot-set! self 'price-per-share x))
(define-method dollars ((self <stock-account>) x) (num-shares self (/ x (price-per-share self)))) (define-method dollars ((self <stock-account>)) (* (num-shares self) (price-per-share self)))
これの上手いのは、deposit と withdraw については何処も書き換えなくてもちゃんと動くところ。
gosh> (define my-stock (make <stock-account>)) my-stock gosh> (num-shares my-stock 10) #<undef> gosh> (price-per-share my-stock 30) #<undef> gosh> (dollars my-stock) 300 gosh> (dollars my-stock 600) #<undef> gosh> (dollars my-stock) 600 gosh> (deposit my-stock 60) #<undef> gosh> (dollars my-stock) 660
(実は、deposit や withdraw の型を省略したので、この場合
オブジェクトの中身
準備が終わった所で、describe を使ってクラスの中を覗いてみる。
gosh> (d <stock-account>) #<class <stock-account>> is an instance of class <class> slots: name : <stock-account> cpl : (#<class <stock-account>> #<class <bank-account>> #<class <o direct-supers: (#<class <bank-account>> #<class <object>>) accessors : ((num-shares . #<slot-accessor <stock-account>.num-shares 0> slots : ((num-shares) (price-per-share) (dollars)) direct-slots: ((num-shares) (price-per-share)) num-instance-slots: 3 direct-subclasses: () direct-methods: (#<method (dollars <stock-account>)> #<method (dollars <stoc initargs : (:name <stock-account> :supers (#<class <bank-account>>) :sl defined-modules: (#<module user>) redefined : #f category : scheme
Smalltalk と同じように Gauche でも、クラスもオブジェクトだ。また、describe で目で調べる以外に、プログラムの中で中身を調べるメソッドが沢山用意されている。クラスのクラスは Smalltalk と違って一つしか無いみたい(という事は、クラスメソッドが無い?)。
gosh> (eq? (class-of <bank-account>) (class-of <stock-account>)) #t
ある引数の型によって同じメソッド名を別の処理に使えるのはどうやって実現してるのかな?普通のオブジェクト指向だったらクラスがメソッドを「知っている」から可能な事だけど。これはメソッドがクラスを知ってないと出来ない事だ。という事で dollars メソッドの中身を眺めてみた。
gosh> (d dollars) #<generic dollars (4)> is an instance of class <generic> slots: name : dollars methods : (#<method (dollars <stock-account>)> #<method (dollars <stoc gosh> (slot-ref dollars 'methods) (#<method (dollars <stock-account>)> #<method (dollars <stock-account> <top>)> #<method (dollars <top> <top>)> #<method (dollars <top>)>) gosh> (d (car (slot-ref dollars 'methods))) #<method (dollars <stock-account>)> is an instance of class <method> slots: required : 1 optional : #f generic : #<generic dollars (4)> specializers: (#<class <stock-account>>) gosh>
describe で色々調べると楽しい!何となく分かってきた!も、もしかして + とかもこんな風に実装されているのだろうか?と思ってみたんだけど、
gosh> (d +) #<subr +> is an instance of class <procedure> slots: required : 0 optional : #t locked : #f info : "+" setter : #f
なんか、思ってたのと違う。どうやら + は再定義できないようだ。ガックリ。。。(追記) と、思ったけど、id:SaitoAtsushi さんのご指摘で + も再定義出来ると分かりました。こんな感じ。
(define-method + ((self <bank-account>) number) (+ (dollars self) number))
gosh> (d my-account) #<<bank-account> 0x1042fd78> is an instance of class <bank-account> slots: dollars : 100 gosh> (+ my-account 50) 150
これはかなり、イイ!
- 参考: 7.5 ジェネリックファンクションとメソッド http://practical-scheme.net/gauche/man/gauche-refj_67.html#SEC188