今度こそinstance_evalとmodule_evalを理解してもらった

ような気がするのでメモ。

「instance_evalとmodule_evalを理解」というのは特異メソッド、インスタンスメソッドの違いを理解して、
必要に応じてinstance_evalとmodule_evalを使い分けられる、ということかと思います。

なので、ゴールとしては、

block = Proc.new do
  def foo
    'foo'
  end
end

というブロックがあったときに、これを使って特異メソッド、インスタンスメソッドを追加できるようになる、ってことでいいのかなと思った。

以下、その前提と問題。答えはきっとid:t-tairaが書いてくれます。

(2010/03/01追記)
id:t-tairaさん回答ありがとー
http://d.hatena.ne.jp/t-taira/20100227/1267232880


特異メソッドとインスタンスメソッド

特異メソッドは「特定のオブジェクトでのみ使えるメソッド」、インスタンスメソッドは「インスタンスを作らないと使えないメソッド」と考えれば良いかなと。
メソッドにはこの2種類しかないので、特異メソッドは「そのクラス(あるいはそのモジュールをincludeした何らかのクラス)のインスタンスを作らなくても使えるメソッド」と言うこともできます。

特異メソッドはObject#singleton_methodsで調べることができて、インスタンスメソッドはModule#instance_methodsで調べることができます。

Time.singleton_methods
#=> ["times", "at", "gm", "utc", "mktime", "_load", "now", "local"]

Time.instance_methods
#=> ["inspect", "tap", "month", "clone", "usec", "public_methods", "getlocal", "object_id", "ctime", "__send__", "instance_variable_defined?", "equal?", "freeze", "hour", "+", "extend", "send", "methods", "-", "year", "hash", "dst?", "strftime", "dup", "to_enum", "getgm", "instance_variables", "zone", "utc?", "eql?", "mday", "instance_eval", "id", "to_i", "singleton_methods", "wday", "taint", "frozen?", "getutc", "instance_variable_get", "enum_for", "instance_of?", "display", "gmtoff", "method", "to_a", "gmt?", "day", "instance_exec", "type", "tv_sec", "to_f", "<", "protected_methods", "<=>", "localtime", "between?", "==", "min", "yday", ">", "===", "_dump", "instance_variable_set", "asctime", "respond_to?", "kind_of?", "gmt_offset", "succ", ">=", "to_s", "utc", "<=", "mon", "class", "tv_usec", "private_methods", "=~", "tainted?", "__id__", "gmtime", "isdst", "untaint", "nil?", "sec", "is_a?", "utc_offset"]

Enumerable.instance_methods
#=> ["count", "partition", "max_by", "member?", "cycle", "each_cons", "entries", "collect", "min", "take", "find_index", "one?", "each_with_index", "grep", "reduce", "min_by", "drop_while", "enum_slice", "reject", "zip", "to_a", "detect", "first", "any?", "sort_by", "inject", "minmax", "drop", "each_slice", "select", "include?", "reverse_each", "find", "group_by", "all?", "minmax_by", "enum_cons", "sort", "map", "max", "take_while", "find_all", "none?", "enum_with_index"]

Time.nowはTimeという特定のオブジェクトに対してのみ呼び出せるので特異メソッド、Time#yearはTime.nowでインスタンスを生成して、それに対して呼び出すのでインスタンスメソッドです。

Time.now.year
#=> 2010

Enumerable#mapは、ArrayなどのEnumerableをincludeしたクラスのインスタンスを生成して使うのでインスタンスメソッドです*1

[1, 2, 3, 4, 5].map{|num| num * 2}
#=> [2, 4, 6, 8, 10]

ブロック

改めて書くと時間がかかるので、この辺を見てみてください。

基本の資料は GoogleDocsで公開
http://docs.google.com/Presentation?id=dgqhvkmp_17n5zbxpgk
コラボレータは名乗り上げてもらって僕が追加。
回答はgithubで公開
http://github.com/akm/llonsen2008_ruby/tree/master

特異メソッドの書き方

特異メソッドを作るには3つの書き方があります。

obj = Object.new
def obj.foo
  "foo"
end

class << obj
  def bar
    "bar"
  end
end

obj.instance_eval do
  def baz
    "baz"
  end
end

インスタンスメソッドの書き方

class A
  def foo
    "foo"
  end
end

module B
  def bar
    "bar"
  end
end

A.module_eval do
  def baz
    "baz"
  end
end

他にもdefine_methodを使う方法もありますが、ここでは割愛。

Q. どちらを使うべきか?

obj = Object.new

class A
end

objというObjectのインスタンスとAというクラスがあり、

block = Proc.new do
  def foo
    'foo'
  end
end

というfooというメソッドを定義するblockがあった場合に、objに特異メソッドとしてfooを、Aにインスタンスメソッドとしてfooを定義するためのコードはどう書けば良いでしょうか?

*1:ただし特定のオブジェクトにextendされるとそれぞれのインスタンスメソッドが特異メソッドとして追加されます。http://www.ruby-lang.org/ja/man/html/Object.html#extend