言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Gauche のオブジェクトのなかみ

暇だったので Gauche のマニュアルを読んでいると、めっちゃ面白い事が分かった。特に、Smalltalk みたいなちゃんとしたオブジェクトがある所。今まで lisp はどれも emacs lisp と似たようなもんだと思ってたのでびっくり。そこで、http://squab.no-ip.com:8080/wiki/835 を基に、Gauche のオブジェクトを覗いてみた。

Bank Account

まず Gauche のクラスを使って預金口座 を作る。define-class にクラス名と親クラス、そして要素の名前を書く。そして make で新しいインスタンスの作成。gosh 内では、さらに describe (d) を使って中を覗く事が出来る。

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

次に、 を継承した株の口座(?) を作る。 は dollars に加えて保有数 num-shares と単価 price-per-share がある。もっと簡潔なやり方もあるらしいが初心者なので愚直にアクセッサを書きます。

(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))

の違う所は、評価額を表す dollars だ。評価額は num-shares と price-per-share から自動的に決まるので、同じ dollars でも別の動作をさせたい。メソッド定義の仮引数を (引数名 クラス名) と書くと、Gauche では引数に応じて別の処理を書く事が出来る。

(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

これはかなり、イイ!