JavaScript、Ajax、HTML5(API)、Ruby…Web及び関連技術の実験&情報公開&制作物紹介。

【覚書】[Ruby]トップレベルに定義したメソッドと同名のメソッドをクラスで定義した場合

| コメント(0) | トラックバック(0)

ちょっとばかしハマったのでメモ。

トップレベルであるメソッドを定義します。
それと同名のメソッドをあるクラスでも定義します。
例えば、

def meth(a) p a; end
class Clazz
    def meth; p self; end
end

こんな感じ。

このとき、クラス Clazz 内で、トップレベルのメソッド meth(a) を呼ぶにはどうしたら良いのか?
もっと言うと、同名のメソッド内で

def meth(a) p a; end
class Clazz
    def meth
        《トップレベルで定義したメソッドを self を引数に渡して呼び出したい!》
    end
end

こんなことしたい場合に、どうすれば良いのか?

...あ、もちろん、
「トップレベルで定義したメソッドと同名のメソッドを定義して、なんてそんな変な設計にしない」
というのが正解なのは分かっています。
仮に、あくまで仮に、そんな事態に陥った時の解決法は? ということですので。

[基本1]トップレベルでメソッドを定義するということ

Rubyでは、def xxx ? end で定義されるものは「メソッド」です。
「メソッド」というからには、必ず何らかのオブジェクトをレシーバーとして持つわけです。

クラス定義内でメソッドを定義すれば、そのレシーバーはそのクラス(またはサブクラス)のオブジェクト(instance)です。
モジュール定義内なら、そのモジュールをinclude(Mix-in)したクラスのオブジェクトです。もしくはextendした場合はそのクラスのクラスメソッドとなり、つまりレシーバーはそのクラス(クラスオブジェクト)になります。

ではトップレベルで定義したら? と言うと。
Kernelモジュールのメソッドとして定義したのと同じ扱いになります。
KernelモジュールはObjectクラスでincludeされています。
Objectクラスは全てのクラスのスーパークラスです。つまり、全てのオブジェクトはObjectクラスのインスタンスメソッドを使用可能です。

つまり。
トップレベルでメソッドを定義すれば、どんなオブジェクトでもレシーバーとなる、イコールいつでもどこでも呼び出しができる、ということになります。

[基本2]メソッド呼び出し時にレシーバーを明示しない場合の挙動

Rubyではメソッドの呼び出しは、obj.xxx()のように所謂「ドット記法」を利用できます。
他にも文法上有効な記法はありますが、とにかく「レシーバー(前置)、メソッド名(後置)」というよくある基本法則で成り立っています。

ところがいくつかのオブジェクト指向言語がそうであるように、Rubyでもレシーバーの記述を省略できます。いきなり「メソッド名」を記述できるわけです。

その場合、self が指定されたと見なされます。
self は、現在の文脈における「自分自身」を表すオブジェクトです。
メソッド定義内なら、そのメソッドを実行しているオブジェクト(レシーバー)、クラス定義内やモジュール定義内ならばそのクラス/モジュール自身。 なおトップレベルでは、main という特殊なオブジェクト(Objectクラスのインスタンス)になります。

いずれにせよ。
「メソッド名」だけを指定した場合は、取り敢えずなんらかのオブジェクトが必ずレシーバーとなり、Objectクラスで定義されているインスタンスメソッド(Kernelモジュールで定義されているものを含む)ならなんでも呼び出し可能なわけです。

よってトップレベルで定義したメソッドは、レシーバーの記述なしに「メソッド名」だけで呼び出しが可能なわけです。
(所謂「関数のように使用できるメソッド」ですね)

前置き長ッ!

トップレベルで定義したメソッドと同名のメソッドを定義するということ

今まで見てきたように、レシーバーの記述なしにメソッド名だけを記述した場合、トップレベルで定義したメソッドを呼び出すことができます。
ところがその仕組み上、レシーバーを省略して呼び出すことができるメソッドはそれ以外にもあります。
現在の文脈のselfが応答できるメソッドならなんでも呼び出すことが可能なのです。

つまり。例えば↓

def meth(a) p a; end
class Clazz
    def meth; p self; end
    def other_meth; meth() end
end

こんな感じに記述されていた場合、メソッドother_meth内のmeth()は、クラスClazzのインスタンスメソッドとしてのmethを呼び出します。
トップレベルで定義されたmethは呼び出されません。

これは、モジュールKernelで一旦定義したメソッドmethを、クラスClazzで再定義(オーバーライド)した形になっています。
オーバーライドなので後から上書き定義した方が有効、つまりClazzクラスのインスタンスメソッドの方が呼び出されるわけです。

(なおメソッドmeth内のpは、クラスClazzで定義されておらず、Kernelモジュールで定義されているメソッドで、よってそれが呼び出されます)

トップレベルで定義したメソッドを明示的に呼び出す方法

さて、本題(やっと)。
元々やりたかった、「クラス内で定義したメソッド内で、トップレベルで定義した同名のメソッドを呼び出したい!」場合。
以下のようにすれば実現可能です。

def meth(a) p a; end
class Clazz
    def meth
        super self
    end
end

ポイントは「同名メソッド定義=オーバーライド」ということ。
superは、オーバーライドしたメソッドから元のメソッドを呼び出す方法。

ただし。これはある種の危険性をはらんでいます。
superは「1つ前」のオーバーライドされたメソッドを呼び出すだけなのです。
つまり、そこでmethメソッドを再定義するより前にどこかで(例えば継承ツリー上の何処かの祖先クラスで)methメソッドを再定義していたら、そちらの方が呼び出されてしまうのです。
また、同名メソッド内からならまだしも、別のメソッド内からだとsuperも使用できません(そのメソッドのオーバーライド元を呼び出してしまうので)。

後者に対応できる別の方法としてaliasを利用して

def meth(a) p a; end
class Clazz
    alias toplevel_meth meth
    def meth
        toplevel_meth self
    end
end

と書くこともできますが、この場合も前者の状況は全く同様(aliasを実行する時点で定義されているmethに別名toplevel_methが与えられるため)です。

どうしても「トップレベルで定義したメソッド」を明示的に指定したければ、こう書くしかありません。↓

def meth(a) p a; end
alias toplevel_meth meth
class Clazz
    def meth
        toplevel_meth self
    end
end

はい。aliasをクラス定義の外(=トップレベル)に逃がしただけですね。
でも、これ。意味ないですよね。

結局、初めからメソッド名を別々にすれば解決じゃん!

......お粗末さまです。

そもそもなんでこんなこと考えたか。

JavaScriptは、Rubyと違いクラスベースではなく「プロトタイプベース」のオブジェクト指向言語です。
JavaScriptでは、function xxx (...) { ... } という書式で「関数」を定義します。
関数は常に何らかのオブジェクトに「バインド」されており、そのバインド先のオブジェクトがレシーバーに当たるものになります(JavaScriptのキーワードthisは常に「バインド先のオブジェクト」を指します)。
特にトップレベルで定義された関数は、グローバルオブジェクト(Webブラウザ上ならばwindowオブジェクト)にバインドされます。

つまり。
もし別の関数内とかでトップレベルで定義したのと同じ名前の関数を定義した場合、トップレベルの関数は window.xxx(); という感じで呼び出すことができるわけです。
また関数の実態もオブジェクトであり、バインド先が何であるかが重要であり、そもそも「関数名」はあまり重要ではないわけです。

その結果として、あるオブジェクトのメソッド的に定義した関数も、実装の実体を(ローカルスコープ内の)別の関数に委譲して処理させる、ということもよくやるのです。グローバルスコープを汚さなかったり、オブジェクトに要らぬプロパティを持たせたりすることもなくて便利なので。

それと同じような考えをRubyに持ち込めないかな、と思って色々試していたときに、そういえばトップレベルと同名のメソッドってどうなの?、と。

もっとクラスベースのオブジェクト指向プログラミングを勉強しなおして、Rubyらしいプログラミング手法を身につけた方が良さげですね。

トラックバック(0)

トラックバックURL: http://www.antimon2.atnifty.com/mt5/mt-tb.cgi/77

コメントする

カテゴリ

月別 アーカイブ

OpenID対応しています OpenIDについて

このブログ記事について

このページは、あんちもん2が2010年5月11日 16:40に書いたブログ記事です。

ひとつ前のブログ記事は「動的にCSSを追加 append_css.js」です。

次のブログ記事は「ひと足早くiPadのWebを考える勉強会」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。