読者です 読者をやめる 読者になる 読者になる

メタプログラミングRubyをさらっと読んだ

Ruby

さらっと、というよりは一発では分かりそうになかったので、というほうが正しいw。ちなみに本書を読む前の自分が持ってた知識レベルは動的ディスパッチ*1とevalの初歩ってくらいです。

メタプログラミングRuby

メタプログラミングRuby

メタプログラミングとかいうとevalとか黒魔術っぽいものを思い浮べるが、そういう技術ばっかり載っているのではなく、強力過ぎる道具で自分の足元撃ち抜かないための線引きがきちんと書いてあったり*2、Rubyの中のことがきちんと知れる構成になっていてよい本だと思った。

Railsの付近の章はまだ全然読んでないけど、それ以外のところで「おおっ」と思ったようなところをメモ。特にスコープのフラット化の付近は読んでて一人ですごいすごい言っていたw。

クラス再訪

  • 「クラスはオブジェクトである」
    • オブジェクトについて当てはまるものはクラスにも当てはまる
    • クラスはClassクラスのインスタンスなのだ
      • なっ、なんだってー

動的メソッド

以下のコードは面白い。動的にメソッドが定義されていく。

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def self.define_component(name)
    define_method(name) do
      info = @data_source.send "get_#{name}_info", @id
      price = @data_source.send "get_#{name}_price", @id
      result = "#{name.to_s.capitalize}: #{info} ($#{price})"
      return "* #{result}" if price >= 100
      result
    end
  end
  
  define_component :mouse
  define_component :cpu
  define_component :keyboard
end

selfが付いていないとエラーで死ぬ。

NoMethodError: undefined method `define_component' for Computer:Class
	from (irb):56:in `<class:Computer>'
	from (irb):40
	from /opt/local/bin/irb1.9:12:in `<main>'

これはクラスがClassクラスのインスタンスであるということが理解できていれば大丈夫なはず。クラスを定義しているときのselfはインスタンスではなく、クラスに対して付いているので、Classクラスに対してdefine_componentをやろうとして死んでしまっているのである。

Computerクラスのオブジェクトがレシーバーになっているんではなくて、Computerクラスがレシーバーになっている、と考えるとしっくりくるかも。

スコープのフラット化

これは結構すごい。外のスコープにある変数をコードの本質を変えずにいかに触れるようにするか。

my_var = "Success"

MyClass = Class.new do
  puts "#{my_var} in the class definition!"

  define_method :my_method do
    puts "#{my_var} in the method!"
  end
end

MyClass.new.my_method

技術的には「入れ子構造のレキスカルスコープ」とかフラットスコープと読んだりするらしい。

スコープの共有化

  • 複数のメソッド間で変数を共有したいが、その他からは見えないようにしたいということが可能になる(すごい)
def define_methods
  shared = 0
  
  Kernel.send :define_method, :counter do
    shared
  end

  Kernel.send :define_method, :inc do |x|
    shared += x
  end
end

define_methods

counter       # => 0
inc(4)
counter       # => 4

instance_eval

オブジェクトのコンテキストでブロックを評価するもの。これもフラットスコープで評価されるために外の変数にアクセスできるようになる。

class MyClass
  def initialize
    @v = 1
  end
end

obj = MyClass.new
obj.instance_eval do
  self        # => #<MyClass:0x3340dc @v=1>
  @v          # => 1
end

v = 2
obj.instance_eval { @v = v }
obj.instance_eval { @v }      # => 2

こやつを使うとprivateな変数にも触れるようになってしまうため、「カプセル化って何だったの...」って感じになってしまいそうだが、irbからさくっと試したいときやテストのときには便利なので、そういうときだけ触るようにすればよい。

*1:Perlとかで昔書いた記憶がある

*2:「やれるのは分かったけど、実際にやっていいの?」とかそういうの