極めるメソッド

$Id: method.rd,v 1.4 2003/06/18 23:28:28 aamine Exp $

極めるシリーズその 2、メソッド使いこなしちゃうぜ講座。

メソッド定義のきほん

よいこのみなさんは知ってるとおり Ruby では

def method_name( arg, arg, opt_arg = default, *rest_args, &block_arg )
  内容…
end

でメソッドが定義できる。メソッドとは何か? 関数とは違うのか。 まあ、似たようなもんだと思ってもいいと思う。 オブジェクト指向教条主義の人なら怒るだろうけど、 実際使う時は理論なんてどうでもいいのだ。 動作を理解した後に呼び方がふさわしくないと思うようになったら それから変えればいいんじゃない?

で内容だが、opt_arg は省略可能な引数。 rest_args も書かなくていいって点では省略可能だが、 それはあくまで数が不特定であるということから派生している。 &引数は使ったことない人もいるだろうが、イテレータブロックを 実体化する(持ちはこべるようにする)ための引数だ。 例えば次のようにすればイテレータブロックをまるなげできる。

def another
  yield 'ok'
end

def meth( &block )
  another( &block )
end

meth do |i|
  puts i
end

これで 'ok' が出力される。 まあこのへんは Rubyist なら常識だ。

動的なメソッド呼び出し

動的な、というのはプログラマがよく使いたがる(かつ好む)言葉だが、 動的なメソッド呼び出し、というのは理解できるだろうか? 例えば、次のような状況を考えてほしい。 条件によって動作を変えたい。条件は、今は文字列の違いとしようか。 条件分岐と言えば if だろうってんで、まずは if で分岐するコード。

if    condition == 'hoge' then
  動作1
elsif condition == 'fuga' then
  動作2
elsif condition == 'nono' then
  動作3
などなど

こういうのは case を使えばもうちょっと楽になるんだった。

case condition
when 'hoge'
  動作1
when 'fuga'
  動作2
when 'nono'
  動作3
などなど

これでいいじゃん。問題ないよね?

全然よくない。こんなの美しくないではないか。プログラマたるものは極限ま で美を追求しなければいかん。それに、分岐が多かったり「動作 n」の部分が めちゃくちゃ長かったらどうだろう。構造が非常に把握しにくくなってしまう ではないか。そういう時に、例えば次のような方法がある。

STR2ID = { 'hoge' => :action1,
           'hoge' => :action2,
           'hoge' => :action3,
           好きなだけ並べる }

def action1() 動作1 end
def action2() 動作2 end
def action3() 動作3 end

__send__ STR2ID[ condition ]

さて、ここに出てくる __send__ がこの項の主題だ。__send__Object の メソッドで、一言で説明すれば「好きなメソッドを呼びだすメソッド」という ことだ。そりゃ、スクリプトで書けば好きなメソッドを呼び出せるのだが、そ れには必要なだけのメソッド呼び出しを実際にその場に書いておかねばならな い。しかし __send__ を使うと、実際に呼び出すメソッドの名前を実行時にう けとって、それを呼び出すことができる。つまり「どのメソッドを呼び出すか」 をスクリプトを書いた時ではなくスクリプトが実行される時まで延ばすことが できる。それが「動的」の意味だ。

ちなみに「:action1」のような記法はシンボルを表す。シンボルってのは、こ れまた常識だけど、任意の文字列と一対一対応するなにかだ。なにか、とぼや かしたのは、1.4 までは整数で 1.5 からは Symbol というオブジェクトだか らだ。シンボルは特に「名前」を扱う時に使う。文字列と比べてよい点は、

というようなところである。

「動的」を別の観点から見てみると、データをコードに変換するという働きで ある。文字列やシンボルはデータ。自分で書くスクリプトはコード。データは プログラムで変更できるがコードは変更できない。変更できないことでプログ ラムの動作の「広がり」が封じられているわけだが、コードをデータに変えて やるとその封印がとける。つまり、プログラムが「広がれる」ようになる。

ところで、lisp なんかはコードが即データで全てのコードは実行時に変更可 能だ。しかし、それでは lisp は最強の言語か? というとやっぱり違う。必 要なのは動的にしたいところだけ構造的に動的にすることであって、全部まる ごと動的にしちゃうっていうのは意味がない。なぜなら、そんなこと言ったら 機械語があればオッケーじゃないか。任意の数字の並び(データ)を実行させる ことができるんだから。

module_eval

定型のメソッドってのはよくある。例えばインスタンス変数を返すだけの メソッド。これをちゃんと定義するとすると、

def item() @item end

となるわけだが、普通こういう場合は attr を使うだろう。

attr :item

これは便利だ。タイプ量も少ないし、うっかり

def item() @itm end

なーんてしてしまう恐れもない。でも、attr とは違う定型定義は できないのだろうか。それができる。module_eval という Module の メソッドを使うと、定型(だけじゃないが)メソッドを定義するための メソッドを定義することができる。

class Module
  def my_attr( name )
    module_eval %-
      def #{name.id2name}() @#{name.id2name} end
    -
  end
end

この my_attrattr と同じ機能(ただし読み出しだけ)を持つメソッドだ。 このメソッドを定義しておけば好きなモジュール定義の中でこのように使える。

class A
  my_attr :item
end

え、クラス定義の中で文を実行できるのを知らなかった? それはいけないなー。 ついでに今、おぼえておこう。クラス定義ってのは実は定義というより 「クラス登録文」っていうような意味で、実は中身はすべて実行される ものなのだ。def は同様にメソッド登録文である。ちなみに、外と中身では 何が違うかというと、環境が違うのだ。環境とは、self とか type とかの ことである。クラス・モジュール定義の中では、まさにその定義中のクラスが self になっている。

ではこういう「メソッド定義をするのためのメソッド」を定義する方法を解説 していこう。まず確認しておくと「%-...-」はただの文字列だ。"..." でもい いが、それだと中に「"」自体を入れることができないので用心のために%文 字列を使っている。使いたいんなら「”」だろーが「’」だろーがヒアドキュ メントだろうが、好きなものを使えばいい。ようするに文字列ならいいのだ。 つまり、module_eval の一般的な使用例はこう。

module_eval 文字列

それから引数の name は、使い方を見てのとおりシンボルを想定しているのだっ た。つまり name.id2name でシンボルが文字列に戻る。それを埋めこんで、定 義したいメソッド(の文字列)を作る。その文字列を module_eval すると、そ れが実行されたモジュールの定義の中にその文字列が書いてあるかのように反 応する。今回のmy_attr では文字列の中身はどうなっていたかというと、

こんなふうになるのだった。で、これをクラス A の定義の中の、 my_attr を呼んだところにそのまま展開すると…

class A
  def item() @item end
end

こうなる。ここまで来れば Aitem() が定義されるのは納得できるだろう。

あとは文字列操作だ。文字列なんだから、好きなように加工して最後に module_eval してやればいろいろなことができる。どんなことができるか? それは、おれが例を挙げなくたって優秀なプログラマならいくらでも実践でき るはずだ。

module_eval (2)

実は module_eval にはまだ使い道がある。それは行番号の変更だ。 ともかく次のコードを見てくれ。

module_eval 'raise', 'another.rb', 200

これを実行すると表示はどうなるか、試してほしい。あとは自分で好きなよう に使おう。「Rubyスクリプト埋め込み系」のアプリケーションでは役に立つこ と間違いなしだ。ちなみに instance_eval も同じ機能を持っている。

module_function

Ruby では module_ という名前はなにかと裏技が多いらしい。 module_functionModule のメソッドで、普通は以下のように使って モジュール関数を作るのだが…

def foo                # とりあえず普通に定義して
  いろいろする
end
module_function :foo   # モジュール関数に変える

実はこんな使い方もあったりする。

module_function

def foo           # 勝手にモジュール関数になる
  いろいろ
end

def bar           # 勝手にモジュール関数になる
  いろいろ
end

ただし undocumented なので気をつけてくれ。