世界のナベアツに Squeak Smalltalk で挑戦する(改訂)…の解説
id:sumim:20080417:p1 で定義されたメソッド群について、おおざっぱにですが説明を加えたいと思います。
まず、簡単なところで「#と:」と「#の時だけ:」。前者は #or: のエイリアス。後者も、ほぼ #ifTrue: のエイリアスですが、引数を改めてブロック化することで #ifTrue: に渡しています。これにより、
true の時だけ:[#アホに]
を、
true の時だけ:#アホに
と、ブロック無しで書けるようにしました。
「#なります」と「#なって」は、レシーバを要素に持つ配列を作って返します。その結果、「#から:まで数えて:」の第二引数で、どんなふうに数値を読み上げるかを決めている条件記述として与えられたブロックを、配列にして渡すことができます。また、この二つのメソッドの返値はいずれも配列なので、
[3の倍数 の時だけ:#アホに]なって, [5の倍数 の時だけ:#犬っぽく]なります
のような記述により、#, による配列の連結が可能となります。これで条件を記したブロックが複数の場合も、それぞれを要素として持つ配列のかたちで「#から:まで数えて:」の第二引数として渡すことができるわけです。
さて。条件ブロック内の最後は「#の倍数」。これの上流で呼んでいる「#各々」が今回の工夫です。
通常、Smalltalk ではループを回す際に要素をブロック内で参照するために、ブロック引数をひとつ用意(宣言)しなければいけません。
1 to: 40 do: [:各々 | Transcript cr; show: 各々]
ただ、それではナベアツの“セリフ”である条件ブロックは(仮に、引数の倍数かどうかを調べるのに #isDivisibleBy: を用いた場合…)、
[:各々 | (各々 isDivisibleBy: 3) の時だけ:#アホに]
のようにいかにもコードっぽく記述しないといけなくなります。これでは興ざめです(たとえ、#isDivisibleBy: に日本語のエイリアスを設けたとしても限界があるでしょう…)。
そこで、実行時スタックに積まれたコンテキストを上流にさかのぼってゆき、「各々」という名前の変数に行き着いたらそれを引っ張ってくる「MethodContext>>#各々」という特殊なメソッドを定義しました。これを用いることでブロックのブロック変数は省くことができ、上のブロックは次のように書けます。
[(thisContext 各々 isDivisibleBy: 3) の時だけ:#アホに]
この「thisContext 各々」は下流メソッド内でも呼ぶことができるので、「thisContext 各々」の内容がレシーバの倍数かどうかを判断するメソッドを「#の倍数」としてあらためて定義することで、
[3の倍数 の時だけ:#アホに]
と書けるようにしました。「#がつく数字」についても同様です。
最後は「#から:まで数えて:」。レシーバから第一引数までを1ずつ繰り上げながら「各々」に代入し(これをあとで「thisContext 各々」でたぐりよせます)、まず第二引数として与えられた条件ブロック群を順に評価して起動すべきメソッド名(セレクタ)を集めた「セレクタ群」を得ます。この「セレクタ群」を #inject:into: することで、数値から条件に合った加工を施した文字列を得て、最終的にトランスクリプトに出力しています。
加工のバリエーションは「#アホに」「#犬っぽく」「#気持ちよく」の三つで、それぞれ「二文字目に $〜 を挿入」「末尾に 'バフッ' を追加」「末尾の文字に合わせた $ぁ、$ぃ、$ぅ、$ぉ および '〜〜ッ' を追加」ということをしています。
これで、コードの内容はおわかりいただけると思います。
1から:40まで数えて:[(3の倍数 と:[3がつく数字])の時だけ:#アホに]なります
以上です。なお、「MethodContext>>#各々」のようなことは、Smalltalk 同様、実行時コンテキストをたぐることができる Io や Rubinius Ruby(Matz Ruby は不可)でならほぼ同じことが可能なはずです。