2008年5月15日木曜日

JavaScript のブロックスコープ - Java, Ruby との比較

1. ブロック内の変数のスコープ

Java のブロックにおける変数のスコープ

Java における「ブロック」とは、「Java言語規定 ブロック及び文」によると、

ブロック(Block) は,中括弧で括られた一連の文及び局所変数宣言文とからなる。

ブロック内における変数のスコープは、「14.3.2 局所変数宣言の有効範囲」によると、

ブロック内で宣言された局所変数の有効範囲は,ブロックの残り部分とし,変数そのものの初期化子を含む。局所変数仮引数の名前は,その有効範囲内の局所変数又は例外仮引数として再宣言してはならない。再宣言すると,コンパイル時エラーとなる。

つまり、{ } で囲まれた中で宣言された変数は、そのブロックの内側でのみ有効となる。

 

JavaScript にブロックスコープはない

これに対して、JavaScript では、「Core JavaScript 1.5 Guide:Block Statement - MDC」によると、

 重要:JavaScript にはブロックスコープがありません

えぇ~ (@_@;) こういう風にデザインされている理由って何だろう?実装のしやすさかな?

I don't have any documentation, but I'd guess that ease of implementation is the motivation.

(Block-scope | Lambda the Ultimate より)

 

2. Ruby のブロックスコープ

404 Blog Not Found:LLいろいろ、スコープいろいろ」によると、

このスコープの「レキシカルさ加減」が、実は言語によって結構違うので、この機会にちょっとつっこんでおくことにする。…

… 、pythonもrubyも、この点においてはむしろJavaScriptに近い挙動を示します。(...)

特にrubyのブロック引数がブロックローカルではない点は、オレオレ詐欺に引っかかったようなみじめさを感じさせずにいられません。

Rubyで関数プログラミング Part 6 【ファーストクラスの関数】」にも、同様の指摘がある。

Rubyの関数オブジェクトは、どうやら「関数」オブジェクトではないそうです。つまり、proc(lambda)で作ることのできるオブジェクトとは、(Smalltalk的な)ブロックであるらしく、|x,y|と引数を指定しても、それは仮引数ではなく、ブロック外の同名の変数(つまり、ブロック外のx,yという名の変数)を上書きしてしまうのだそうです。

これに関して、Matzにっき(2005-03-09) の「Ruby 2.0 ブロックローカル変数」に、以下のように書かれている。

Rubyのブロックスコープをなんとかしたいとずっと考えてきた。で、今までにもいろいろなアイディアを練ってきたのだが、なかなか決められなかった。最近になってやっと「これなら」というものが出来上がったように思う。

仕様は以下の通り。

  • ブロックパラメータはローカル変数のみ
  • ブロックパラメータのスコープはブロックローカル
  • ブロックパラメータ名が外側のローカル変数と重複したらエラー(前田くんの勝ち)※
  • ブロックパラメータの後ろに「; 変数」という形でそのブロック内でだけ有効な変数名を指定できる※
  • これらの変数も重複はエラーになる
  • ブロック内で初出の変数のスコープはブロックローカル(今まで通り)※
  • メソッド内で使われたスコープが終わってしまった変数への参照は警告になる※

追記 (2011.12.2) : Ruby 1.9 では、ブロック変数のスコープの仕様が変更された。

第6回 Ruby 1.9に起きた変化 - O'Reilly Japan Community Blog」によると、

Rubyにはブロックというものがあります。縦棒(|)の中に変数があって、(直前に書かれたイテレータの各要素が)渡されパラメータとして代入される。これはもともとループの抽象化として誕生したので、棒の間はループの各要素が代入される場所だったんです。(ブロックパラメータは)任意の変数、つまりグローバル変数でも、ローカル変数でも、配列でも大丈夫。何でも置けたんです。

ところが1.9からはちょっと変わっていて、グローバル変数を置こうとするともうダメ。これまではオブジェクトの属性に代入できたり、配列のスライスを呼び出せたのも、できなくなりました。それと同時に、(ブロックパラメータに指定された)ローカル変数のスコープは、ブロックの範囲内にとどまるということ。

 

3. JavaScript で、変数をブロックスコープのように扱うには、関数を使う

まるごとJavaScript & Ajax ! Vol.1 によると、

JavaScript の変数のスコープは「{}」で囲われたブロック単位ではなく、関数単位になります。(p96)

(function(){
 var tmp = 0;
 for( var i=0; i<10; i++ ){
 // などなど、初期化処理を行う
 }
})();

「(function(){ ~ }」が無名関数の定義になります。その直後に再びカッコ()が置かれているため、その無名関数がその場で実行されるというトリックです。(p97)

同様のことが Block-scope | Lambda the UltimateStatic scoping に書かれている。

 

Greasemonkey において、変数をブロックスコープのようにする方法

 まるごとJavaScript & Ajax ! Vol.1 の「ものぐさな Greasemonkey の飼いかた」の最初のスクリプト(p158)に、以下のようにあったのは、変数をブロックスコープにして実行するための工夫だったのかぁ。

(function () {
 //ここに実際のコードを書く
})();

 

ブックマークレットにおいて、変数をブロックスコープのようにする方法

追記(2008.5.27):

において、参考にした

にも、同様の手法について言及されている。

変数をローカルにするには

無名ファンクションのインスタンスを生成して、これを呼び出すようにするといいようです。

 

4. JavaScript 1.7 の let

New in JavaScript 1.7 - MDC4 Block scope with let には、

The let statement provides a way to associate values with variables, constants, and functions within the scope of a block, without affecting the values of like-named variables outside the block.

として、次のサンプルが挙げられていた。

var x = 5;
var y = 0;

let (x = x+10, y = 12) {
  print(x+y + "\n");
}

print((x + y) + "\n");