69
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

メタプログラミングRuby 勉強メモ

Last updated at Posted at 2014-11-22

メタプログラミング Ruby 勉強メモ

Ruby を勉強し直そうと思い進められたのがこれ。
メタプログラミングRuby
Paolo Perrotta
固定リンク: http://www.amazon.co.jp/dp/4048687158

ググると Ruby 2.0 に対応した英書が出ているようですが、
とりあえず1.9に対応している和書でお勉強。

オブジェクトモデル

オープンクラス

既存のクラスに新たに独自のメソッドを追加すること

例えば整数を2倍したかったら普通はこうかく。

def to_twice(num)
  num * 2
end

puts to_twice(2) # => 4

それをオープンクラスを利用して書き換えると

class Fixnum
  def to_twice
    self * 2
  end
end

puts 2.to_twice # => 4

こうなる。
標準の Fixnum クラスに独自の自身を2倍するメソッドを追加できた。

すでにクラスに同名のメソッドがあると上書きしてしまうので注意
(これをモンキーパッチというらしい)

クラス

クラスもオブジェクトらしい。
どういうこと?

puts "hello".class # => String
puts String.class  # => Class

これは String クラスが Class クラスのオブジェクトということを示している。

メソッドの呼び出し

Ruby ではメソッドの呼び出しを以下の順で行う

  1. メソッドを探す これをメソッド探索という
  2. メソッドを実行する。これには self というものが必要

例えば

class Hoge
  def say
    puts "hogehoge"
  end
end

class Hige < Hoge
end

hi = Hige.new
hi.say #=> "hogehoge"

        Hoge クラスがもつ say メソッド
          ↑
hi オブジェクト → Hige クラス

みたいな感じで呼ばれる。
これを「右へ一歩、それから上へ」ルールと呼ぶ。
そしてこのクラスのつながりを継承チェーンと呼ぶ
じゃこの場合はどうなるか?

module A
  def say
    puts "A"
  end
end

module B
  def say
    puts "B"
  end
end

class C
  include A
  include B
end

class D < C
end

d = D.new
d.say # ここで出力されるのは A ? それとも B ?

こたえば B。
なぜかというと、module をインクルードすると継承チェーンでクラスの真上にインクルードした module がつながるから。
図にすると

A モジュール

B モジュール

C クラス

D クラス

という順になる。最初はCクラスの真上にAモジュールがつながり、
つぎにBをインクルードしたのでCクラスの真上にBモジュールがつながるため。
メソッド探索は下から行うので最初に見つかる say メソッドは B モジュールのメソッドになる。

メソッド

Ruby は動的にメソッドを定義したり呼び出したりできる。

send

その前に send について少しだけ。

メソッドの呼び出し方は2つある。

class Hoge
  def say (name)
    puts "Hello, #{name}"
  end
end

h = Hoge.new
h.say("issei")
h.send(:say, "issei")

見ての通り、オブジェクト.send(<呼びたいメソッド>, <引数>)でメソッドが呼べちゃう。
何が起きるかって言うと、呼びたいメソッドを変数にすることができちゃう。
これを動的ディスパッチと呼ぶ

define_method

実行的にメソッドを定義することもできる。

class Hoge
  define_method :say do |name|
    puts "Hello, #{name}"
  end
end

h = Hoge.new
h.send(:say, "issei") # => "Hello, issei"

send と define_method を使うと面白いことができるようになる。

動的メソッド呼び出しの例

書中では Test::Unit の例が出されていた。

method_names = public_instance_method(true)
tests = method_names.delete_if {|method_name| method_name != /^test./

これでtestと先頭についてメソッドを収集して send で実行しているよう。

動的メソッド定義の例

たとえばこんなクソコードを考えてみる。
プリンタっぽいものを想定した KusoPrinter クラス。

class KusoPrinter
  def initialize(paper_size)
     @paper_size = paper_size
  end

  def print_A4
    puts @paper_size.get_info_A4 
  end

  def print_B4
    puts @paper_size.get_info_B4
  end
end

class PaperSize
  def get_info_A4
    return "A4 desu"
  end

  def get_info_B4
    return "B4 desu"
  end
end

ps  = PaperSize.new
prt = KusoPrinter.new(ps)

prt.print_A4 #=> "A4 desu"

ほとんど同じメソッドがいっぱい並んでる。

似た部分を抽出してメソッドを作る。

class KusoPrinter
  def initialize(paper_size)
     @paper_size = paper_size
  end

  def print_A4
    self.print_ps("A4")
  end

  def print_B4
    self.print_ps("B4")
  end

  def print_ps(paper_size)
    puts @paper_size.send("get_info_#{paper_size}")
  end
end

class PaperSize
  def get_info_A4
    return "A4 desu"
  end

  def get_info_B4
    return "B4 desu"
  end
end

ps  = PaperSize.new
prt = KusoPrinter.new(ps)

prt.print_A4 #=> "A4 desu"

このままだと用紙サイズを増やす必要がある場合、
そのたびにメソッドを追加しなければならない

これを send と define_method を使うと

class KusoPrinter
  def initialize(paper_size)
     @paper_size = paper_size
  end

  def self.print_ps(paper_size)
    define_method "print_#{paper_size}" do
      puts @paper_size.send("get_info_#{paper_size}")
    end
  end

  print_ps("A4")
  print_ps("B4")
end

class PaperSize
  def get_info_A4
    return "A4 desu"
  end

  def get_info_B4
    return "B4 desu"
  end
end

ps  = PaperSize.new
prt = KusoPrinter.new(ps)

prt.print_A4 #=> "A4 desu"

と重複するコードを削除することができた。

さらにPaperSizeクラスでのget_info_xxxx メソッドが増えても、
KusoPrinter クラスを書き換えないで済むようにできる。

class KusoPrinter
  def initialize(paper_size)
     @paper_size = paper_size
     @paper_size.methods.grep(/^get_info_(.*)$/) {
       KusoPrinter.print_ps($1)
     }
  end

  def self.print_ps(paper_size)
    define_method "print_#{paper_size}" do
      puts @paper_size.send("get_info_#{paper_size}")
    end
  end

end

class PaperSize
  def get_info_A4
    return "A4 desu"
  end

  def get_info_B4
    return "B4 desu"
  end

  def get_info_B3
    return "B3 desu"
  end

end

ps  = PaperSize.new
prt = KusoPrinter.new(ps)

prt.print_A4 #=> "A4 desu"
prt.print_B3 #=> "B3 desu"

PaperSize がもつメソッド名からKusoPrinter のメソッドを動的に生成している!
すごい!


手軽な図を書く方法、ソフトあれば教えてください!

umanodaさん
ソースコードの間違い訂正ありがとうございます!

69
68
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
68

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?