「3日目」の章、の2日目。
- 作者: Bruce A. Tate,まつもとゆきひろ,田和勝
- 出版社/メーカー: オーム社
- 発売日: 2011/07/23
- メディア: 単行本(ソフトカバー)
- 購入: 9人 クリック: 230回
- この商品を含むブログ (64件) を見る
メッセージで自分が持っていないスロットを指定された場合にはプロトタイプにforward
Rubyには呼び出されたメソッドが未定義だった場合に呼び出される[http://doc.ruby-lang.org/ja/1.8.7/method/Object/i/method_missing.html:title=method_missing]
メソッドがありますが、Ioで同様の動作をするのがforward
メソッドです。
Io> foo := Object clone ==> Object_0x4e4bf0: Io> foo hoge Exception: Object does not respond to 'hoge' --------- Object hoge Command Line 1 Io> foo forward := method("[foo " .. call message name .."]") ==> method( "[foo " .. call message name .. "]" ) Io> foo hoge ==> [foo hoge]
foo
にhoge
メッセージを送ったとき、forward
に細工をする前はfoo
はhoge
に応答できませんが、forward
を定義してやるとメッセージに応答するメソッドがなかった場合に動作しているのがわかります。
Ruby on Railsはこのしくみを効果的に使って実現しています(記憶が正しければ)。
練習の題材に、役に立ちそうな立たなそうな「ローマ数字を解釈する」というのを選んでみました。
まず。Number VI
とやると6
と返ってくるようにしてみます。
このためにRomanNumeral
というオブジェクトをインポートしていますが、これは話題の中心でないので後ろの方で解説します。文字列に対してたとえば"VI" fromRomanNumeral
とすると数値の6が返るようなしくみを実現している、とだけ覚えておいてください。
まず。そのままではメッセージに応答できないことを確認。
Io> Number VI Exception: Number does not respond to 'VI' --------- Number VI Command Line 1
RomanNumeral
を読み込んで、Number
のforward
を書き換えます。
Io> RomanNumeral ==> RomanNumeral_0x5f6840: C = 67 D = 68 I = 73 L = 76 M = 77 RomanNumeralDigits = "MDCLXVI" V = 86 X = 88 fromRomanNumeral = method(...) romanNumeralDigit = method(s, ten, five, one, ...) type = "RomanNumeral" Io> Number forward := method(n := call message name fromRomanNumeral; if(n != 0, n, nil)) ==> method( n := call message name fromRomanNumeral; if(n != 0, n, nil) )
実行結果。
Io> Number VI ==> 6 Io> Number III ==> 3 Io> Number IIII ==> nil
メッセージ名をローマ数字と解釈して数値に変換されているのがわかります。
マスター名前空間でもforwardする
Ioではすべてのメッセージにレシーバがいて、レシーバを指定しなかったばあいはマスター名前空間(いわゆるグローバルな空間)のオブジェクトにメッセージが送られます。
と、いうこととは。マスター名前空間にもforward
を定義することができます。
Io> forward := method(n := call message name fromRomanNumeral; if(n != 0, n, nil)) ==> method( n := call message name fromRomanNumeral; if(n != 0, n, nil) ) Io> VI ==> 6 Io> III ==> 3 Io> MCMXCIX ==> 1999
計算もできます(数値ですから)。
Io> XXXIII + LXV ==> 98 Io> XXXIII + LXV * X ==> 683
なんかあっさり片付いてしまった気がする。
そんなわけで、つづく。あとひとつ、の予定。
ローマ数字パーサの実装
ここからは補足。
昨日の下書きをもとにIoで書き下したものです。
本当は正規表現を使うつもりだったんですが、うまくいかなかったので急遽パーサを書いてみました。また本題と違うところで時間をとってしまった…。
RomanNumeral := Object clone do( RomanNumeralDigits := "MDCLXVI" M := RomanNumeralDigits at(0) D := RomanNumeralDigits at(1) C := RomanNumeralDigits at(2) L := RomanNumeralDigits at(3) X := RomanNumeralDigits at(4) V := RomanNumeralDigits at(5) I := RomanNumeralDigits at(6) romanNumeralDigit := method(s, ten, five, one, if((s isEmpty not) and (s at(0) == one), s removeAt(0) if((s isEmpty not) and (s at(0) == five), s removeAt(0); return(4)) if((s isEmpty not) and (s at(0) == ten), s removeAt(0); return(9)) value := 1 while((value < 3) and (s isEmpty not) and (s at(0) == one), value = value + 1 s removeAt(0) ) return(value) ) if((s isEmpty not) and (s at(0) == five), s removeAt(0) value := 5 while((value < 8) and (s isEmpty not) and (s at(0) == one), value = value + 1 s removeAt(0) ) return(value) ) 0 ) fromRomanNumeral := method( s := self asMutable m := 0 while((m < 3) and (s isEmpty not) and (s at(0) == M), m = m + 1 s removeAt(0) ) c := romanNumeralDigit(s, M, D, C) x := romanNumeralDigit(s, C, L, X) i := romanNumeralDigit(s, X, V, I) if(s isEmpty, m * 1000 + c * 100 + x * 10 + i, 0) ) ) Sequence appendProto(RomanNumeral)
最後にSequence appendProto(RomanNumeral)
とやって、RomanNumeral
をSequence
のプロトタイプに追加しています。
Ioのオブジェクトは複数のプロトタイプを持てるようで、こうやってあとからプロトタイプを追加できることを今回知りました。これで多重継承あるいはmix-inのようなことができるようになっているようです。
これを「RomanNumeral.io」という名前で保存します。
使うとき。
Ioでは最初にオブジェクトを参照したとき、そのオブジェクトと同名のファイル(拡張子は.io)を読み込こんで評価するしくみになっているようです(参照:Io Programming Guide / Importing)。
ここではRomanNumeral
オブジェクトを積極的になにかに使うというわけではないので、単にオブジェクトの名前を書いて評価されるようにしています。
RomanNumeral args := System args args removeFirst args foreach(arg, writeln(arg, " => ", arg fromRomanNumeral))
これをsample.ioという名前で保存して実行した結果がこれ。
$ io sample.io I III IV V VIII IX X L C D M MCMXCIX MDCCCLXXXVIII I => 1 III => 3 IV => 4 V => 5 VIII => 8 IX => 9 X => 10 L => 50 C => 100 D => 500 M => 1000 MCMXCIX => 1999 MDCCCLXXXVIII => 1888