Edited at

ころんころんの話をしよう。もしくはモジュールの名前空間について。

More than 1 year has passed since last update.


ころんころん

::をどう発音するか自分は寡聞にして知らないのだが、筆者は心のなかでころんころんと呼んでいる。

ちょっとかわいい音感だ。

そんなころんころんに関して、ふだん意識せずに使っている人もいるだろうから、短いながらもころんころんについて解説したい。


ころんころんとは何者か

::である。

ここで言うところの::rubyの演算子の中で最も優先度の高いスコープ演算子であるころんころんだ。

rubyでは演算子の組み合わせである自己代入演算子は再定義できない。

先刻ご承知の通り再定義に関してはおおらかなrubyではあるが、ころんころんころんころんな自己代入演算子であるからにして、これもまったき再定義できない1

つまるところころんころんrubyという言語に組み込まれた制御構造である。


ころんころんはどう使うのか

:: を使うに、あまり卓越していない小輩の身ではあるのだけれど、一例の一つや二つ、二例三例と紹介するには吝かではない。

::はスコープ演算子であるのだが、これは名前空間を表すために利用される。

rubyではモジュールもクラスも定義するとその名前を定数として持ったオブジェクトになる。ちなみにClassクラスはModuleクラスのサブクラスである。


モジュールの定義

$ Module.class

#=> Class
$ mod = Module.new #無名の module を作成
$ mod.class
#=> Module

モジュールを定義したとき、モジュール名と同じ定数にモジュールオブジェクトが代入される。これはクラスを定義した場合も同じである。

まあ上記の例では無名のモジュールクラスを作成しているので名前となるべき定数がない。無名の場合は最初に代入された定数がその名前になる。以下はその用例だが、筆者はこのコードの実際の使用例を見たことがない。


無名モジュールの名付け

$ mod.name

#=> nil
$ hoge = mod
$ hoge.name
#=> nil
$ Hoge = mod
#=> Hoge
$ mod.name
#=> "Hoge"

話が逸れた。

とにかく::は、このモジュールやクラス内で定義された定数名を外部から参照するときに使用されるのだ。


階層構造を持ったモジュールの宣言

module Foo

module Bar; end
end

階層構造を持ったモジュールを参照してみよう。


階層構造をもったモジュールの呼び出し

$ Foo

#=> Foo
$ Bar
# => NameError: uninitialized constant Bar
$ Foo::Bar
#=> Foo::Bar

ついに::の用例が出てきたぞ!

Fooの中で宣言されたBarはそのままでは参照できない。参照するためには正しくFoo::Barとその階層構造通りに場所を示す必要がある。

では以下のように宣言すればどうなるであろうか。


階層構造をもった?モジュールの宣言

module Moge

module ::Piyo; end
end

おそらく読者諸兄は上掲の用例を見かけたことが有るであろう。::は左辺が省略できるのである。


階層構造をもった?モジュールの呼び出し

$ Moge

#=> Moge
$ Piyo
# => Piyo
$ Moge::Piyo
#=> NameError: uninitialized constant Moge::Piyo

先ほどと、結果が変わった。PiyoMogeの下で宣言したように見えて、Mogeの配下にはいないのだ。そしてどうやらPiyoMogeは同じ階層にいるようなのだ。

それでは::の省略された左辺とはなんであろうか、また、::を書かずともモジュールを宣言した場合にいったいどこの配下にいるのか。上掲の無名モジュールの用例に続けて、モジュールの定義された場所を参照するためにそのスーパークラスを確認してみよう。


無名モジュールのスーパークラス

$ mod.class.superclass

#=> Object

Objectクラス!

そう、Objectクラスに定義されるのだ。ころんころんはたんにころんころんではなく、オブジェクトころんころんだったのだ。

ここに定義されている定数をトップレベルの定数と呼び、そしてこの場所こそが::がスコープ演算子として使用された場合の省略された左辺なのである。


オブジェクトころんころんの名前空間の使い方

前項で::の省略された左辺で示す場所とはObjectクラスの配下であると示した。

また、無名モジュールを宣言した際にObjectクラスに定義されていることも示した。

ところで先日、筆者は異なる場所でトップレベルの配下に宣言されたモジュールで、複数形と名詞系が同じ語の名前空間が衝突したために、それを解決すべく新しく名前空間を切った。

そしてそれを解決する過程で多くのオブジェクトころんころんを目にした。もちろん筆者が過去に書いたものもある。本当は、今いる場所をきちんと考えれば不要なはずなのにだ。申し訳ない。


オブジェクトころんころんはなるべくやめよう

Objectクラスの名前空間はすぐに使いやすい定数を入れてしまうので、名前が衝突しやすい。

いつどんな予期せぬ参照が起きるかわからない繊細なものである。

正しく名前空間を切って、どこにも所属しないオブジェクトころんころんな定数の宣言を減らし、有限な資源を効率良く使うようにしていくことが肝要なのである2。そういったころんころんといった語感を楽しむ余裕を持ったプログラミングを心がけたいものである。

重要なのは、汎用的な名前をオブジェクトころんころんの下でなるべく定義しないことなのだ。


付録:「ころんころんの話をしよう。」の翻訳にあたって

筆者がふだん扱うのは巨大なRailsプロジェクトであり、一つの大きなドメインに属するものである。

しかしその膨大なコード量、クラス数に比して頻出する用語は少ない。rubyに限らずあらゆる言語の名前空間と優先順位戦争もそうであるが、この有限なる「使いやすい名前」という大切な資源を有効活用することがいま求められている。

どこで宣言するか、どんなオブジェクトであって欲しいか、何を期待するのか、プログラミングをするにあたって名付けの悩みは計り知れない。その中であって、名前空間という重要な視点について、読みやすく、多くの用例を持って解説した本稿「ころんころんの話をしよう。」が一筋の光明になれば幸いである。


注意書き及び参考文献


同じモジュール名での定義の追加

$ module Hoge

$ module Fuga;end
$ end
$
$ module Hoge
$ module Moga;end
$ end
$ Hoge.constants
#=> [:Fuga, :Moga]









    • rubyではモジュールが既に定義されているとき、さらに同じモジュール名でモジュール定義を書くとモジュールの定義の追加になる。同じモジュール名での定義の追加を参照のこと。

    • 上記のとおりなので同じ名前で定義してもメソッドや内部の定数名が被らなければ問題は起きにくい。クラスも同様。


      • であるが、結局解決順が問題になるのでだいたいRailsのせいと筆者は思っているようである。


      • table_name_prefixとか曲者だけどみんなどうやって対処しているんだろうか。



        • app/models/で定義されるのでlib/と呼び出し順が異なり、名前空間が被ると……。