こんにちは!九兵衛です
最近Rubyに触れていて、「あれ? なんでグローバル変数やインスタンス変数って、初期化しないまま参照するとnil
になっちゃうの?他の変数みたいに例外発生させないのはどういう思想があるんだろう?」と思ったので、ちょっと調べてみました。この記事では、その理由と歴史的な背景、Rubyの設計方針などをゆる〜くまとめてみます。最後に参考文献ものせているので、よかったらチェックしてみてください
前提:Rubyの変数の種類
Rubyには、ざっと以下のような変数があります。
-
ローカル変数:
foo
のように先頭がアルファベットやアンダースコアで始まる -
グローバル変数:
$foo
のように先頭が$
で始まる -
インスタンス変数:
@foo
のように先頭が@
で始まる -
クラス変数:
@@foo
のように@@
で始まる
通常、ローカル変数に対して「定義(代入)してない変数をそのまま参照」するとエラーが起こるのは有名ですよね。例えば:
puts my_local_var
こんな風にローカル変数を宣言してない状態で呼び出すと、NameError が発生します。一方、グローバル変数とインスタンス変数は、定義しないまま参照しても nil が返ってきます。
どうしてグローバル変数・インスタンス変数はnilになるようにしたの?
1. Rubyの方針: 「エラーよりはnilの方がマシ」?
Rubyの設計を行ったまつもとゆきひろ(Matz)さんは、Rubyを「人間に優しく、自然に扱える」ように作ろうという思想を持っていたとされています。エラーを出すことよりも、とりあえずnilを返したほうがメソッドチェーンなどのプログラムが書きやすいという考え方が背景にありそう。
例えばオブジェクトのインスタンス変数って、オブジェクトが作られる時点で持っていなくても、あとからぽんっと追加できるじゃないですか。Rubyはインスタンス変数を動的に生やすことができるため、まだ宣言されていないインスタンス変数があってもエラーを出すのではなく、存在しなかったらnilねとするほうが自然で使いやすい気がしています。
グローバル変数についても同じで、「まだ代入されていないよ」という意味合いとしてnilが返るようになっています。すべてのスコープから参照可能という自由度ゆえに、変数が存在しなくても、とりあえず落ちずに受け皿としてnilを返す設計がなされているわけですね。
2. エラーの暴発を避けるため
Rubyができた当時(1990年代後半)、オブジェクト指向スクリプト言語はまだそれほど多くなく、Perl や Python などと比較されることが多かったそうです。Perlのグローバル変数は $ で始まるけど、Rubyのように「未定義時はnilにする」という仕様ではなかったりして、いろいろと挙動が違いました。
Rubyのコミュニティでは、「エラーで止まるよりは多少大らかに動いて欲しい」「意図せず変数参照しちゃってエラーになるのも困るし…」という議論があったようです。結果として、「エラーを出さずにnilを返しておけば、後続処理でnilを判定すればいいよね」という考え方が根付いていったと考えられています。
3. Rubyの動的性 (Dynamic nature) と整合
Rubyは動的な言語として有名です。クラスやモジュールに対しても実行時にメソッドを追加したり、インスタンス変数を追加したりと、非常に柔軟に動作します。この柔軟さを保つためには、「まだ定義されていない変数を参照しちゃったらすぐエラー」というよりも、「とりあえずnilを返す」ほうがすんなり馴染むんですよね。
もちろん、動的だからこそ注意して使わないと、気づかないうちに変数名を打ち間違えていたなんてバグが発生する可能性もありますが、それはRubyの持つ自由度とのトレードオフと言えます。「動的な言語ってこういうデメリットもあるよね」という感じで割り切って使うスタイルがRuby流というわけです。
歴史的背景: Matzの思想とRuby誕生の頃
Rubyの開発が始まったのは1993年頃で、当時Matzさんは「より自然で使いやすいスクリプト言語」を目指して試行錯誤していました。Perlは非常に表現力が高く便利な言語だったのですが、Matzさん的には「オブジェクト指向機能がもっと欲しい」「Perlよりも人間に優しくしたい」という思いがあったそうです。
Rubyはオブジェクト指向を根幹に置きつつ、当時からスクリプト言語の気軽さを大事にしてきました。その結果、「エラーがすぐ起きる」よりは「柔軟に書ける」ことが優先される部分があります。
たとえばC++やJavaだと、クラスのフィールド(=インスタンス変数)を参照するときに「ちゃんとメンバー変数として宣言されているか?」をコンパイラがチェックしてくれますが、Rubyは動的にインスタンス変数を増やすことができますよね。そういった動的性を重視する設計においては、未初期化の変数を参照してもnilになる方が自然だった、というわけです。
ローカル変数との違い
上で少しふれましたが、ローカル変数は「宣言なしで参照するとNameError」が起こります。これは、ローカル変数が使われるスコープが明確なためです。逆に言うと、グローバル変数やインスタンス変数はスコープが広かったり、動的に作られる可能性があるため、Rubyはこの2種類に関しては未初期化でもnilを返すようにしているんですね。
たとえば以下のようなコード:
def greet
puts $message # => nil
end
greet
$message が宣言されていなくても、nil と出力されるだけでプログラムは止まりません。一方で:
def greet_local
puts message_local # => NameError: undefined local variable or method `message_local' ...
end
greet_local
こちらはNameErrorが発生して落ちてしまいます。同じ変数参照なのに、Rubyの仕様としてはっきりと挙動が分かれているんですね。
実際どう活用するといいの?
パターン1: 「存在しないけど、とりあえずnilで続行したい」
アプリケーションによっては、「インスタンス変数を初期化し忘れた!」という状況でも、落ちずに後続の処理へ行ってほしいケースがあります。その場合、インスタンス変数が未定義であってもnilが返ってくるというRubyの仕様はとても便利ですよね。
パターン2: 「メモ化パターン」
Rubyでよくやるメモ化(ある処理の結果をインスタンス変数に保存しておき、次回からはその値を使い回す)も、この仕様のおかげでスムーズに書けます。
def expensive_calculation
@result ||= some_heavy_process
end
@ result が初めて呼び出されるときは未定義ですが、Rubyはnilで扱ってくれるので、@ result ||= ...が問題なく動きます。この書き方はRubyではよく見るテクニックですよね
まとめ
Rubyの動的性と人間に優しい設計という思想から、グローバル変数・インスタンス変数は未初期化だとnilが返るようにしている。
実行が止まるより、とりあえずnilを返して柔軟に扱えるほうが便利という判断が大きい。
ローカル変数はスコープがはっきりしているため、未定義での参照はNameErrorになる(ここは他のスクリプト言語ともだいぶ違う仕様)。
歴史的にはPerlなどの先行言語の仕様を参考にしつつ、よりオブジェクト指向的で「自然に使える」設計を目指した結果が今のRubyの仕様になっている。
Rubyを書いていると、どうしても「動的に変数を扱う」ことの気軽さに慣れてしまって、バグに気づきにくいケースも正直あります。ただ、それ以上に「よく書かれたRubyコードは読みやすく、可愛らしい」(と私は思ってます ) ので、これからも上手に活用していきたいですね。
参考文献・リンク
Programming Ruby 1.9 & 2.0 (The Pickaxe Book)
Dave Thomas, Chad Fowler, and Andy Huntによる名著。Rubyの歴史や設計思想にも触れられています。
The Ruby Programming Language
David Flanagan, Yukihiro Matsumoto 著。Matz自身が執筆に参加しているので、Rubyのコアな思想についても読みやすい一冊。
The Well-Grounded Rubyist
David A. Black によるRuby解説書。Rubyらしい書き方や言語仕様を丁寧に説明しています。
Ruby公式ドキュメント
最新の言語仕様や組み込みライブラリのドキュメントがまとまっています。細かい挙動を確認するときに便利!
Rubyの変数の仕組みを改めて調べると、言語設計の思想が透けて見えて面白いですよね。ぜひ皆さんもご自身のコードで「未初期化のグローバル変数やインスタンス変数の挙動」を試してみてください。それでは、また