Rubyのブロック引数宣言の“ | ”(バーティカルバー)のルーツをSmalltalk-72に探す

アラン・ケイの“オブジェクト指向”というアイデアをもとに(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72で遊んでみるシリーズ(記事一覧→Smalltalk-72で学ぶOOPの原点 Advent Calendar 2019 - Qiita)ですが、今回はコラムとしてRubyのブロック変数の宣言に用いられる | のルーツを探ります。


Rubyのブロックと引数宣言

Rubyにはブロック付きメソッド呼び出しのための構文があって、メソッド呼び出しの末尾に{ }で括った処理の記述(ブロック)を付すことで、その処理を引数(オブジェクト)としてメソッドに渡すことができます。ブロックは0個以上の引数をとることでき、その冒頭で| |で括って仮引数(ブロック引数)をカンマで区切るなどして列挙することで宣言できます。

[1,2,3].collect{ |a| a * 2 } #=> [2,4,6]

このブロック引数をくくる| |という奇妙な記法がSmalltalk-80(以降の今のSmalltalk)を模倣により生まれた経緯については「まつもとゆきひろ 言語のしくみ」の「1-5 言語デザイン入門(後編) ブロック」に書かれているとおりですが、ではこの「|」はどこから来たのか?というのが今回のお話です。

Smalltalk-72のクラス定義における「|

LivelyWeb版のSmalltalk-72エミュレーター(より正確には、LivelyWebでALTO/Novaを部分的にエミュレートし、そこに当時保存されたSmalltalk-72が動いている状態のメモリダンプを読み込ませることで再現された処理系。St-72アドカレ後半で使ったsumimによるちょい直し版はこちら)の画面にある「Open The ST-72 Manual」ボタンを押すとダウンロードして読むことができる1976年刊のSmalltalk-72の操作マニュアルには次のように、変数の種類(一時変数、インスタンス変数、クラス変数の宣言)の区切りには「|」を使うことが示されています。

f:id:sumim:20191223165612p:plain
1976年刊のドキュメントには変数宣言の区切りは「:」ではなく「|」が使われている

しかし、実はこれに従って記述しようとしてもLivelyWeb版Smalltalk-72ではクラスを定義することができません(そもそもグリフが「|」である記号を入力できないので…^^;)。「ALLDEFS」という別のボタンをクリックして呼び出すことができる1974年頃に使われていたブートストラップコードを参照すると、なるほど、1976年より前のある時点では区切りには「|」ではなく「:」を使っていたことが分かります。

f:id:sumim:20191223170430p:plain
ALLDEFSにある「turtle」クラスの定義

1974年の実装から1976年に刊行された操作マニュアルが対象にした処理系に至るどこかの時点で|が使われるようになり、それがSmalltalk-80以降の一時変数宣言に使われた「|」 、ひいてはRubyのブロック引数をくくる「|」につながったというわけです。

「区切り」か「くくり」か?

1976年刊の操作マニュアルが対象にしているSmalltalk-72処理系のクラス定義にに使われた「|」は、あくまで「区切り」としてでしたが、Smalltalk-80以降の今のSmalltalk(一時変数)やRuby(ブロック引数)では「くくり」として使われています。

Smalltalk-80を第三世代、Smalltalk-72を第一世代とすると、その間に開発された第二世代のSmalltalk-76ではまだ「|」は「区切り」として使われていました。ただし、Smalltalk-72のように変数の種類を区切るためではなく、メソッド定義においてメッセージ記述を模した「メッセージパターン」(メソッド名と仮引数の宣言記述を兼ねる)と一時変数の宣言の区画の区切りとして…でした。

f:id:sumim:20191223174043p:plain
Smalltal-76のArrayクラスのメソッド「all←」の定義(「all←val」がメッセージパターン、うち「val」が仮引数宣言。「i」が一時変数宣言。[…]内がメソッドの記述)

これは想像ですが、この後のSmalltalk-80以降の今のSmalltalkでは、ブロック(Rubyとは違い、単独でリテラル的な記述が可能な第一級のオブジェクト)の導入に伴ってメソッド本体を[ ]でくくる記法(<メッセージパターン> | <一時変数宣言> [ <メソッド処理> ])をやめにしたため、一時変数との区切りとしての「|」がひとつでは足りなくなり、メソッド本体との間にも「|」を入れる(<メッセージパターン> | <一時変数宣言> | <メソッド本体>)ことで結果的に「|」でくくる記法が成立したのではないでしょうか。

ともあれ、Smalltalk-80以降の「|」でくくるのがRubyのブロック引数をくくっている「|」のルーツでFA…かというとそうではなく、話はもう少し複雑なようです。

Smalltalk-80以降のブロック引数宣言

先述の「まつもとゆきひろ 言語のしくみ」にはもう答が書かれているのですが、当初Rubyのブロック引数宣言の「|」は当該宣言部分とブロック処理記述部分の区切りとしての意味を持っていたのが、その後廃止されて今の「くくり」になったのだそうです。

f:id:sumim:20191223174951p:plain
まつもとゆきひろ 言語のしくみ」より

Smalltalk-80以降の今のSmalltalkのブロック変数宣言は次のように書きます。

#(1 2 3) collect: [:a | a * 2] "=> #(2 4 6) "

これも想像の域を出ないのですが、Smalltalk-80以降のブロックでは、メソッドにおいて「|」が「くくり」として意味が変わっていく中にあっても相変わらず「|」を区切りとして用い続けていて(これは今のSmalltalkに至るまで「くくり」に変わることはなかった…)、それを模したRubyが今度はSmalltalkのメソッドにおける「|」とは経緯や理由は違うものの結果的には同じ収斂進化を経て「くくり」に落ち着いた…というのが正解に近いような気がします。

おまけ:Smalltalk-80のブロック引数の前の「:」について

こちらについては、Lispのキーワードを模したものかと当初思っていたのですが、さにあらず。メソッドにおける仮引数宣言を兼ねたメッセージパターンと同じものをブロック記述にも応用するにあたり、ブロックにはメソッドのメソッド名に当たる「セレクタ-」およびそれを構成する「キーワード」が無いので、コロンだけを残し、:a のような記法になったというのが正解でした。

ただ、Smalltalk-72を見ていると、メッセージ中の続くトークンを評価して変数に取り込む「:<変数名>」というイディオム(「:」というアクション=オブジェクト に対する<変数名>メッセージの送信式)があり、これも引数を変数に代入するという似たような意味を持つので、まったくの無関係ではないような気もします。

以上、Rubyファンはおろか、今のSmalltalkファンにとってもけっこうどうでもいいマニアックな話でした。^^;