「2日目」のつづきのつづき。念のためもう一度言うと、「2日目」というのは、Ioのエントリを書き始めて2日目という意味でなくて、本の章の名前です。今日は「2日目」の3日目です。
- 作者: Bruce A. Tate,まつもとゆきひろ,田和勝
- 出版社/メーカー: オーム社
- 発売日: 2011/07/23
- メディア: 単行本(ソフトカバー)
- 購入: 9人 クリック: 230回
- この商品を含むブログ (64件) を見る
メッセージ
Ioはオブジェクト指向なので、「オブジェクトにメッセージを送る」というのがプログラムを駆動する基本になります。
メッセージの内容を知りたいばあい、メソッド内でcall
メソッドが使えます。
Io> Object myMethod := method(call println) ==> method( call println ) Io> Object myMethod Call_0x4af930: ==> Call_0x4af930:
「call
メソッド」というと、なにかを呼ぶ(コールする)という印象がありますが、実体はCall
オブジェクトをプロトタイプに持つオブジェクトの値です。なので、なにかを呼ぶというよりも「呼び出しという事象」をあらわすものととらえた方がよさそうです。動詞のcallでなくて名詞のcallですね。
Call
オブジェクトには呼び出し元を示すsender
、呼び出し先を示すtarget
、メッセージを示すmessage
というメソッドがあります。
実際の様子を見てみます。結果を見やすくするためasString
を書き換えてオブジェクトの名前を表示するように細工しています。
Io> Lobby asString := "LOBBY" ==> LOBBY Io> foo := Object clone ==> Object_0x3a11b0: Io> foo asString := "FOO" ==> FOO Io> foo myMethod := method(writeln("target:", call target, " / sender:", call sender, " / message:", call message)) ==> method( writeln("target:", call target, " / sender:", call sender, " / message:", call message) ) Io> foo myMethod target:FOO / sender:LOBBY / message:myMethod ==> nil
マスター名前空間Lobby
からfoo
へメッセージmyMethod
を送っている様子がわかります。
またcall message
で得られるオブジェクトはMessage
オブジェクトのクローンで、argAt
メソッドで引数を取り出すことができます。
Io> foo myMethod2 := method(writeln("first arg:", call message argAt(0), " / second arg:", call message argAt(1), " / third arg:", call message argAt(2))) ==> method( writeln("first arg:", call message argAt(0), " / second arg:", call message argAt(1), " / third arg:", call message argAt(2)) ) Io> foo myMethod2(1,2,3) first arg:1 / second arg:2 / third arg:3 ==> nil Io> foo myMethod2(1,2) first arg:1 / second arg:2 / third arg:nil ==> nil
下の例は第3引数を指定しなかったためargAt(2)
の値がnil
になっています。
ところで。
メソッドの定義をするとき、仮引数を指定することができました。
Io> Object showA := method(s, s println) ==> method(s, s println )
ここでs
が仮引数。
一方で、上記のようにcall message argAt(n)
でも引数を得ることができます。
Io> Object showB := method(call message argAt(0) println) ==> method( call message argAt(0) println )
このAとBで何が違うのか。試してみます。
Io> Object showA(1) 1 ==> 1 Io> Object showB(1) 1 ==> 1 Io> Object showA("hoge") hoge ==> hoge Io> Object showB("hoge") "hoge" ==> "hoge" Io> Object showA(1 + 2) 3 ==> 3 Io> Object showB(1 + 2) 1 +(2) ==> 1 +(2)
Bのばあい、メッセージが評価されずそのまま渡されているのがわかります。条件分岐のところで書いた遅延評価はこのようなしくみで実現されているようです。
とはいえ。どこかでメッセージを評価しないと値を得られません。メッセージを評価するにはdoMessage
メソッドを使います。
Bのメソッドを次のように書き換えます。
Io> Object showB := method(doMessage(call message argAt(0)) println) ==> method( doMessage(call message argAt(0)) println )
これでさきほどの例を試してみます。
Io> Object showB(1 + 2) 3 ==> 3
メッセージが評価されたのがわかります。
次に。foo
のmyMethod
から、foo
のmyName
を引数に与えてA、Bのそれぞれのメソッドを実行してみます。
Io> foo myName := "FOO" ==> FOO Io> foo myMethod := method(Object showA(myName); Object showB(myName)) ==> method( Object showA(myName); Object showB(myName) )
実行結果。
Io> foo myMethod FOO Exception: Object does not respond to 'myName' --------- Object myName Command Line 1 Object showB Command Line 1 Object myMethod Command Line 1
エラーメッセージは「Object
にはmyName
に対応できない」。つまりObject
はmyName
を持っていないと主張しています。
何が起こったのか。
上で説明したようにcall message argAt(0)
は評価前のメッセージそのもを示しています。今回のばあいmyName
です。そのためdoMessage(call message argAt(0)) println
の意味はdoMessage(myName) println
となります。
このときdoMessage(myMessage)
はObject
で実行されているため、myName
はObject
へのメッセージとして評価されてしまいます。しかしObject
はmyName
を持っていないためエラーとなった、というのが事の真相です。
メッセージが未評価のままそのまま渡されるので、評価したときの文脈(context)によって評価結果が異なる、という動作をIoではするようです。
一見、面倒で危なっかしく見えますが(実際いろいろ危険だとは思うけど)、メソッドが文脈を知っているためメッセージの評価(いつ評価するのか、何に対して評価するのか)を柔軟に操作することができるようになっています。
それはそれとして。今回のような場合はどうしたらよいのか。
myName
がObject
へのメッセージでなく、送信元のfoo
へのメッセージと解釈させればよいので、doMessage
を送信元、つまりcall sender
に適用することで期待する結果を得る事ができるようになります。
showB
をさらに書き換え。
Io> Object showB := method(call sender doMessage(call message argAt(0)) println) ==> method( call sender doMessage(call message argAt(0)) println )
これでfoo myMethod
を実行してみると。
Io> foo myMethod FOO FOO ==> FOO
このようにA、Bとも同じ結果になりました。
ちなみに。逆の視点で見ると。Aのように書いたばあい(仮引数を書いたばあい)は、メソッドの呼び出しの前に引数が評価される、ということになりそうです。
これらをふまえて。
本の例にもあったunless
(if
と逆の分岐)を実装。
Io> unless := method((call sender doMessage(call message argAt(0))) ifFalse(call sender doMessage(call message argAt(1))) ifTrue(call sender doMessage(call message argAt(2)))) ==> method( (call sender doMessage(call message argAt(0))) ifFalse(call sender doMessage(call message argAt(1))) ifTrue(call sender doMessage(call message argAt(2))) ) Io> unless(true, "FALSE" println, "TRUE" println) TRUE ==> true Io> unless(false, "FALSE" println, "TRUE" println) FALSE ==> false
はい。とても面倒です。
Ioの作者も面倒と思ったようで、頻繁にでてくるであろうcall sender doMessage(call message argAt(n))
は、call evalArgAt(n)
と短く書けるようになっています。
Io> unless := method( (call evalArgAt(0)) ifFalse(call evalArgAt(1)) ifTrue(call evalArgAt(2))) ==> method( (call evalArgAt(0)) ifFalse(call evalArgAt(1)) ifTrue(call evalArgAt(2)) ) Io> unless(true, "FALSE" println, "TRUE" println) TRUE ==> true Io> unless(false, "FALSE" println, "TRUE" println) FALSE ==> false
このようなショートカットはいくつか用意されています。
元の形 | ショートカット |
---|---|
call message argAt(n) |
call argAt(n) |
call message argCount |
call argCount |
call sender doMessage(call message argAt(n)) |
call evalArgAt(n) |
この他に、すべての引数を送信元の文脈で評価した結果をリストで返すcall evalArgs
というものもあります。
と、いうところで。ようやく「2日目」終了