言語間(Ruby, Javascript, Coffeescript )のスコープの違い
〇 はじめに
いろいろ言語をやっていると、
スコープの違いに気をつけないと、
バグの原因になったり、あとあと後悔するプログラムになってしまうと思うので、
一回、スコープに関してまとめておこうと思う。
(素人プログラマの理解ですので、正確性は保証できませんが、
おおよそ間違ってはいないと信じています。 間違いがあれば指摘ください)
始める前に、私が以前勘違いしていたので、一応書いておきますが、
変数のスコープの話と、メソッド(関数)の探索、という話は
別のようです。なんとなく、Rubyだと識別子の最後に、() が不要なので、
同じような管理をされているように思ってしまいますが、別のようです。
〇 変数の種類
では始めに、変数の種類に注目。
Ruby:
グローバル変数、クラス変数、インスタンス変数、ローカル変数
Javascript:
グローバル変数、ローカル変数
Coffeescript:
グローバル変数、インスタンス変数、ローカル変数
今回、スコープを考える場合、これらの変数のうち、どの言語においても
ローカル変数 についてだけ考えれば十分である。
なぜなら、グローバル変数は、どこからでも参照できる変数なので、全く問題なし。
そして、Rubyにおける、クラス変数や、インスタンス変数 というのは、
クラスの構成要素の中で、どういう位置に存在するかという問題だけで、
(一般的なスコープという概念や議論とは無縁であり)
それほど難しく考えなくてもよい。
少し余談になりますが、
今更ながら、Rubyの、このクラス変数や、インスタンス変数という変数の存在、
さらに、これらが、接頭辞によって、判別できるというのは、大発明だと思う。
(Matz氏の昔のブログに、お子さんをあやしている時に思いついたと書いてあったように思うので、
神からの啓示なのかもしれない (^^) )
〇 ローカル変数のスコープ
では、ローカル変数に入ります。
スコープというのは、その変数が、どこから参照できるかというものです。
基本的に、現代のプログラミング言語の多くが、レキシカルスコープという、
その変数が書かれているソースコードを順に見れば、
その変数にどういう値が、代入されているかがわかるようなスコープを採用しています。
(その対局にあるのが、ダイナミックスコープで、EmacsLispが採用しており、
変数を一度宣言すると、同名の変数は、呼び出し側であっても同じものを指します。
変数名の衝突などの問題がありますが、(私はよくわかりませんが)大きなメリットもあるのでしょう。
http://kreisel.fam.cx/webmaster/clog/img/www.ice.nuie.nagoya-u.ac.jp/~h003149b/lang/comparison.html )
まず、Rubyのローカル変数を見ていきます。
ローカル変数のスコープというくらいなので、どこかにスコープの切れ目が必要です。
言語によっては、中括弧 { } で、スコープのレベルが既定されるようですが、
Rubyの場合では、スコープの切れ目は
ブロックや、メソッド定義や、クラス/モジュール定義 の部分です。
変数が宣言された位置からブロック、メソッド定義、またはクラス/モジュール定義 の終わりまでがスコープになります。
( http://doc.ruby-lang.org/ja/1.9.3/doc/spec=2fvariables.html#local )
さらに、Rubyの場合のスコープの特徴は、
基本的に、下位(内側)のレベルからは参照できないということです。
しかし、これには例外があり、
ブロックでは、内側から参照できます。
この性質のおかげで、Rubyはクロージャというものを作成することができます。
つまり、Rubyのローカル変数のスコープは、
・ レキシカルスコープ
・ クラス/モジュール定義、 メソッド定義、 ブロック 単位
・ 内側(下位) からは参照できない
・ ブロックでは例外的に内側から外側を参照できるため、クロージャ(類似)を作成できる
という性質を持っています。
〇 Javascriptのスコープ
Javascriptの話です。
Javascriptでは、ローカル変数は、var を用いて宣言します。(宣言と初期化は別)
一点だけ例外があり、トップレベルでは、varを用いて宣言しても、グローバル変数扱いになるようです。
次に、Javascript におけるローカル変数の扱いに関してです。
まず、Javascriptのローカル変数も、レキシカルスコープです。
Javascriptのスコープの区切りは、
ブロックレベルでなく、関数定義レベルのみ、です。
つまり、function ** () { }
でのみ、区切られることになります。
さらに、Javascriptと、Rubyのローカル変数の大きな違いがあり、
基本的に、下位(内側)のレベルにもスコープは有効ということです。
逆に言うと、(同じレベルで宣言されていない場合)、下位(内側)から、外側の変数を参照できるということです。
この際に気になるのが、外側と内側で同名の変数が宣言された場合です。
しかし、心配ありません、Javascriptでは、再度変数宣言することで、シャドウイングできます。
ただし、var をつけて、きちんと宣言することが重要です。
ここまでまとめると、Javascriptのローカル変数のスコープは
・ レキシカルスコープ
・ 関数定義 単位
・ 内側(下位)から外側が参照可能
・ シャドウイングできる。 ※ ただし内側が明示的に var 宣言必要。
・ Rubyと比較すると、Rubyのブロックでの、スコープのルールと同じ。
※関数の仮引数は、varを使って宣言された場合と同様になる。
ということになります。
〇 Coffeescriptのスコープ
Coffeescriptですが、
Javascriptベースで、さらにJavascriptを簡素にしたものです。
クラスの概念があるため、インスタンス変数があり、@マーク から始めると定義できます。
ローカル変数に関しては、
特に、varなどローカル変数宣言のための文法はなく、初期化と同時に宣言されます。
トップレベルで変数を定義すると、グローバル変数定義になりえますが、
デフォルトでは、function{ } でファイル全体が囲まれた形で、Javascriptにコンパイルされるので、
グローバル変数になりません。
トップレベルの定義をグローバル変数にしたい場合は、
コンパイルの段階で、-b , --bare オプションをつけます。
ローカル変数に関しては、関数定義レベルでスコープの区切りがあり Javascript同様です
(正確にはJavascriptにコンパイルされた時の関数レベルで区切りがある。つまり、クラスではクラス定義や、メソッド定義レベル)
しかし、実は、大きな違いがあります。
Javascriptでは、シャドーイングがありましたが、Coffeescriptでは、シャドーイングがありません。
そのため、関数の外側に、同名で変数が定義されていると、
その外側の変数を参照してしまいます。
そのシャドーイングがないという仕様に対しては、議論されていますが、
作者としては、普通、グローバルに変数定義しないことなどを理由に、この仕様のままのようです。
ここまでまとめると、Coffeescriptのローカル変数のスコープは
・ レキシカルスコープ
・ 関数定義 単位
・ 内側(下位)から外側が参照可能
・ シャドウイングされない !!
・ Rubyと比較すると、Rubyのブロックでの、スコープのルールと同様。
〇 ローカル変数に関する、ベストプラクティスは。 (個人の意見)
・ Rubyはお気楽。 クロージャ様のものを作りたいときは、ブロックやlambdaで環境を保持できる。
・ Javascriptは、クロージャを作りたい時以外は、var しておくと無難
・ Coffeescriptは、トップレベルには変数を宣言しないように。 内側は外側で同名の変数がないか気にかける。(深くしすぎない方がよさそう。)
本日のブログ記事は(旧)BSDライセンス で提供されています。Copyright : toshi-san
最後に、いろいろ調べている時に、今後よもうかなと思ったブログ記事へのリンク。
有用なリンク:http://hacklabo.com/?cat=27