エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Ioが面白い・その0 の その7 他はロッカールームに戻ったがフォワードはぎりぎりまでシュート練習を続ける

「3日目」の章、の2日目。

7つの言語 7つの世界

7つの言語 7つの世界

メッセージで自分が持っていないスロットを指定された場合にはプロトタイプに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]

foohogeメッセージを送ったとき、forwardに細工をする前はfoohogeに応答できませんが、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を読み込んで、Numberforwardを書き換えます。

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)とやって、RomanNumeralSequenceのプロトタイプに追加しています。
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