火曜日に帰国したのだが、寝込んだりしていたので今日が帰国後最初の出社になる。 今回はおみやげはなし。というか、本当に時間がなくて家族にもろくにおみやげを買う暇がなかった。
Rubyの設計で失敗したと思ってる点は実はいくつもあるのだが、 そのうち最大のものはおそらくローカル変数のスコープだ。
現在のRubyのローカル変数のスコープは
というものだ。後者が曲者だ。このスコープにより、 ブロックの内部ではじめて登場したローカル変数はブロックの内部でだけ有効で、 ブロックの終了とともに消えるというルールが成立する。
一見するとそんなに変なルールではない。実際に私も最初に思いついた (closureのためのローカル変数が必要で導入した)時には、結構いいアイディアだと思っていた。
ところが問題はRubyの特徴である宣言がないところにある。 つまり、あるローカル変数の有効範囲がどこまでなのかがひとめでわからない。 ブロックが始まる前に初期化されているかどうかは、スコープの先頭までさかのぼって見ないといけない。 あるいはたまたま変数名が重なっちゃったりすると思わぬ挙動を引き起こすことになる。
そんな探さなくちゃいけないほど長いメソッドを書くことや、 偶然重複しちゃうような安易な名前を使うことに問題があるといえばそうなのだが、 「変数のスコープがひとめでわからない」というのはRuby的には面白くない。
で、機会があれば直そうとずっと考えていたのだ。
直す方法はいろいろ考えられたが、長らく考えて結局これがベストと思えるものは以下の通り。
要するに宣言なしにネストするスコープを導入しようとするから問題なわけで、 最初に設計した時点では宣言を避けることにこだわり過ぎていたようだ。 今回はブロックパラメータを宣言として利用しようということ。
この際ブロックパラメータと同じ名前のローカル変数がすでにあった場合にはどうするべきかという課題がある。 これには、「名前を気にしたくない、重複を許すべき」派と 「重複は悪である、許すべきでない」派がいる。私(や前田くん)は後者だが、 世の中には前者も多いようだ。今のところ、「許すが警告が出る」という どちらにも嬉しくない両者痛み分けの対応を考えている。
以前のアイディアの中には
などもあったが、前者は簡潔な記法ではあるが「宣言」としては目立たなすぎるので不採用。 後者もそんなに多用することはないだろうということになった。局所変数が必要であれば
local {|a,b,c| ... }
というようなローカル変数導入用メソッドを用意すればよいだろう。 ブロックパラメータはローカル変数に限るというルールにより、 パラメタ宣言は代入の左辺よりもメソッド定義の仮引数に近いこととなり、 あるいはデフォルト値を使って
let {|a=1,b=2,c=3| ... }
のような書き方を許すようになるかもしれない。
結局採用しなかったアイディアの中でもっとも大がかりなものは、 ブロックローカル変数は、現在のように最初に宣言されたブロックに所属するのではなく、 もっとも外側で参照されたブロックに所属するというものだ。
def foo(a,c) a.each{|e| e.each_with_index{|ee,i| break if ee == c } puts i # ここでiが参照されている # よってiはこのブロックに所属 } end
結構面白いし、今でもうまく運用できそうな気もしている。 しかし、「説明するのが面倒なルールは良くないルール」の原則によりボツになったのだった。