カーリーブレイス記法による多重代入もどきを復活させる


SqueakSmalltalk におけるカーリーブレイス(中括弧)を使った記述は、前身である Apple Smalltalk 時代にラリー・テスラーの手により組み込まれた、配列の特殊な表記法を受け継いだものです。


参考:Squeak のコード中によく中括弧を見かけるけど、ありゃ何のためにあるの?


通常 Smalltalk では、配列(an Array)のリテラル記述にはその要素にリテラルオブジェクト(数値、文字、文字列、シンボル。擬変数の true、false、nil は含まず)しか使用できないという制約があります。そのかわり LISP のリストっぽく、スペースで区切ってシンプルに記述できるメリット(と感じるかどうかは人それぞれですが…)も享受できるわけですが。

#('this' #is $a 10)

(配列リテラル中では、シンボルの # は省略可能なので #('this' is $a 10) でも同じ)


なお、最近の Squeak システムの Smalltalk では true、false、nil もこれに使えるようにする(気持ちは痛いほどよくわかるけれど、いろいろな意味で非常に美しくない)仕様変更がひょんなことで採択され、一部にたいへんな顰蹙をかっています。w


では、変数に束縛した値などリテラルオブジェクト以外のオブジェクトを要素に含めたい場合はどうするかというと、Array class (ArrayedCollection class) >> #with:、#with:with:、#with:with:with:、…を使うか、an OrderedCollection といった追加が可能なコレクションに「add: anElementOrExpression」メッセージを送信して組み立てる(どうしても配列でなければならないなら、その後、改めて asArray を送信するなどして配列に変換する)必要があります。

| a b |
a := 3. b := 4.

^ Array with: a + b with: 'abc', 'def' with: 'abc' first
=> #(7 'abcdef' $a)
| a b collection |
a := 3. b := 4.

collection := OrderedCollection new.
collection add: a + b;
           add: 'abc', 'def';
           add: 'abc' first.

^ collection asArray
=> #(7 'abcdef' $a)


おのおのの要素を自動生成できる場合には、Array class (SequenceableCollection class) >> #streamContents: でストリームを介してそれら要素を次々に流し込むようにして配列を作ることもできます。が、配列をリテラルとして書きたいという局面においては、そうした条件が整うことはまあまれでしょう。例として適切ではありませんが、#streamContents: の使いかたはこんなかんじ。

| a b |
a := b := 1.

^ Array streamContents: [:stream |
   [a < 100] whileTrue: [stream nextPut: a. b := a + (a := b)]]
=> #(1 1 2 3 5 8 13 21 34 55 89)


で、こうした状況を不便と思った(のかどうだかは知りませんが(^_^;))、知る人ぞ知るかのラリー・テスラーが、リテラルオブジェクト以外の要素を含む配列でもリテラルっぽく書けるように拡張したのがくだんのカーリーブレイス記法です。中括弧の中に、ピリオドで区切った式を記述することで、それぞれの式の評価結果を要素にした配列を動的に生成できます。

| a b |
a := 3. b := 4.

^ {a + b. 'abc', 'def'. 'abc' first}

(念のためここで“カンマ”は区切りではなく、コレクションの結合に使われるセレクタ、つまりメソッド名、です。したがって 'abc','def' は、'abc' に「,'def'」というメッセージを送信して #, という名前のメソッド起動を期待するひとつの式。)

=> #(7 'abcdef' $a)


なおくどいようですがこの記法は、Apple SmalltalkSqueak、あと、Smalltalk 処理系ではありませんが Squeak に強い影響を受けて作られた Slate、というように一部の限られた Smalltalk(と、それに準ずる)処理系でしか通用しない非常に特殊なものです。SqueakSmalltalk門中の皆さまにおかれましては、ご注意あれかし。



さて。Apple Smalltalk には、このカーリーブレイス記法の特性をいかした派生的な拡張も行なわれました。#caseOf:、#caseOf:otherwise: というセレクタを使った、switch-case 構文っぽいメッセージ式がそのひとつです。

'abcd' atRandom
   caseOf: {
      [$a] -> ['apple'].
      [$b] -> ['banana'].
      [$c] -> ['cherry']}
   otherwise: ['dummy']
=> 'cherry'


ちなみに、制御構造をも“メッセージング”で表現するという Smalltalk の“ドグマ”にのっとっているふうに見せているものの、#ifTrue:ifFalse: などを用いた制御式がそうであるように、この #caseOf:otherwise もコンパイル時にインライン展開されてしまうため、実行時に実際にはメッセージ送信(「caseOf: anArray otherwise: aBlock」メッセージの送信とそれに伴う #caseOf:otherwise: メソッドの起動)は行なわれてはいません。これは、コンパイル後のバイトコードを見れば容易に確認できます。

['ab' atRandom caseOf: {[$a]->['apple']} otherwise: ['dummy']] method symbolic
...
38 <26> pushConstant: 'ab'
39 <D5> send: atRandom
40 <24> pushConstant: $a
41 <B6> send: =
42 <99> jumpFalse: 45
43 <23> pushConstant: 'apple'
44 <90> jumpTo: 46
45 <22> pushConstant: 'dummy'
46 ...

愚直にいわゆる goto 文(#jumpFalse:、#jumpTo:)でひきずり(?)回していますね(^_^;)。なんと、{ } 内にある式で、通常なら、レシーバをキーとして、キーと値のペア(an Association)を作る -> aValue の送信すら行なわれていません。

(#key -> 'value') class   " => Association "

もし、文面通りにコンパイルされているならば、対応するバイトコードが入るはずです。

[#key -> 'value'] method symbolic
...
30 <23> pushConstant: #key
31 <24> pushConstant: 'value'
32 <E2> send: ->
33 ...

で、前置きと余談が長くなりましたが、ようやく本題。 この #caseOf:otherwise: 同様、やはりこのカーリーブレイスを使って実現されたもうひとつの拡張が、表題にもあります「多重代入もどき」です。この機能は Squeak 2.6 以前の Smalltalk では使えましたが、その後、廃止されました。

| a b |
a := b := nil.

{a. b} := #(3 4).

^ {a. b}
=> #(3 4)

2つの変数に束縛されている値の交換もすっきり書けます。

| a b |
a := 3. b := 4.

{a. b} := {b. a}.

^ {a. b}
=> #(4 3)


これを使えば、id:sumim:20060209:p1 の call/cc 版はこんなふうに、オリジナルの Lua コードにより近いかたちで書き換えることができそうです。

co := [:vals | [:a2 :b2 |
   | rr ss |
   print value: {'co-body'. a2. b2}.

   {rr} := foo1 value: a2 + 1.
   print value: {'co-body'. rr}.

   {rr. ss} := yield value: {true. a2 + b2. a2 - b2}.
   print value: {'co-body'. rr. ss}.

   tmps := yield value: {true. b2}.
 
   yield value: {false. 'cannot resume dead coroutine'}] valueWithArguments: vals].


{aa. bb} := resume value: {co. 1. 10}.
print value: {'main'. aa. bb}.

{aa. bb. cc} := resume value: {co. 'r'}.
print value: {'main'. aa. bb. cc}.

{aa. bb} := resume value: {co. 'x'. 'y'}.
print value: {'main'. aa. bb}.

{aa. bb} := resume value: {co. 'x'. 'y'}.
print value: {'main'. aa. bb}


うーむ。すばらしい! ということで、現行の Squeak 3.8 でも多重代入を(あくまで遊び程度に)使うことができるように、廃止された機能を復活できないか調べてみたところ、BraseNode から削除されたいくつかのメソッドを復活させて、Parser >> #expression と AssignmentNode >> #emitForValue:on: 、#emitForValue:on: にちょっと加筆するだけでよいことがわかりました。


http://squab.no-ip.com:8080/collab/uploads/61/braceassign.cs.gz



ただ、結果として残念ながらこのチェンジセットでは目的の call/cc 版を上のように書き換えて動かすことはできません。なぜなら、call/cc 版では、Continuation だけでなくブロックをクロージャにする ClosureCompiler も使っているのですが、これを機能させるにあたり SmaCCSmalltalk Compiler Compiler)というパーサージェネレータで作られた新しいコンパイラに差し替えられてしまっているため、今回の機能復活の影響を及ぼすことができないからです。恥ずかしながら SmaCC の使い方はよくわからないので、悔しいけれど今回はここまで止まり…。