*eval 方法


如果字串實際上是一段程式碼,Ruby可以使用eval方法執行。例如:
>> eval("a = 1; puts a")
1
=> nil
>> eval("def some; puts 'some'; end")
=> nil
>> eval("some")
some
=> nil
>>


在哪個環境下執行eval,預設就會取得哪個環境中的區域變數。例如:
>> STATEMENT = "puts a"
=> "puts a"
>> a = 10
=> 10
>> eval(STATEMENT)
10
=> nil
>> def some
>>     a = 20
>>     eval(STATEMENT)
>> end
=> nil
>> some
20
=> nil
>>


eval的第二個參數可以接受Binding實例,代表執行時區域變數綁定的環境,可以使用binding方法取得當時執行時環境的Binding實例,指定了Binding實例,就可以取得Binding實例綁定的區域變數。例如:
>> def other(b)
>>     a = 30
>>     eval(STATEMENT, b)
>> end
=> nil
>> other(binding)
10
=> nil
>>


物件有個instance_eval,可以將指定的程式區塊,視作為實例方法環境中執行的程序。例如:
>> class Some
>>     def initialize(value)
>>         @value = value
>>     end
>> end
=> nil
>> s1 = Some.new(10)
=> #<Some:0x87eca0 @value=10>
>> s2 = Some.new(20)
=> #<Some:0x288fc48 @value=20>
>> s1.instance_eval { puts @value }
10
=> nil
>> s2.instance_eval { puts @value }
20
=> nil
>>


所以若你也知道物件內部實作,而你也願意,就可以偷出private來執行。例如:
>> class Some
>>     private
>>     def private_mth
>>         puts "private mth"
>>     end
>> end
=> nil
>> s3 = Some.new(30)
=> #<Some:0x2795250 @value=30>
>> s3.instance_eval { private_mth }
private mth
=> nil
>> s3.private_mth
NoMethodError: private method `private_mth' called for #<Some:0x2795250 @value=30>
        from (irb):34
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


有個與instance_eval類似的方法是instance_exec,這個方法可以帶有引數,指定的引數會成為區塊參數的值。例如:
>> s3.instance_exec("arg") { |p| private_mth; puts p }
private mth
arg
=> nil
>>


類別有個class_eval方法,可以將指定的程式區塊置於類別環境中執行。例如:
>> Some.class_eval do
?>     def self.class_mth
>>         puts "class_mth"
>>     end
>> end
=> nil
>> Some.class_mth
class_mth
=> nil
>>


有了class_eval方法,你可以開啟任何類別追加定義,即使是單例類別或匿名類別。例如:
>> o = Object.new
=> #<Object:0x2699340>
>> o.singleton_class.class_eval do
?>     def some
>>         puts "some"
>>     end
>> end
=> nil
>> o.some
some
=> nil
>> x = Class.new {
?>     def other
>>         puts "other"
>>     end
>> }.new
=> #<#<Class:0x2549f70>:0x2549ef8>
>> x.other
other
=> nil
>> x.class.class_eval do
?>     def xyz
>>         puts "xyz"
>>     end
>> end
=> nil
>> x.xyz
xyz
=> nil
>>


由於程式區塊可以使用區塊外的區域變數,所以instance_eval、class_eval方法,開啟了將區域變數帶入實例環境或類別環境的一種方式。例如:
>> a = 10
=> 10
>> class Some
>>     private
>>     def some(p)
>>         puts p
>>     end
>> end
=> nil
>> s = Some.new
=> #<Some:0x2821230>
>> s.instance_eval { some(a) }
10
=> nil
>> class Other
>>     puts a
>> end
NameError: undefined local variable or method `a' for Other:Class
        from (irb):17:in `<class:Other>'
        from (irb):16
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>> class Other; end
=> nil
>> Other.class_eval { puts a }
10
=> nil
>>


不過要注意,只要是def,一定是開啟一個新的作用範圍。所以:
>> a = 10
=> 10
>> class Some; end
=> nil
>> Some.class_eval do
?>     def some
>>         puts a
>>     end
>> end
=> nil
>> Some.new.some
NameError: undefined local variable or method `a' for #<Some:0x26c9940>
        from (irb):5:in `some'
        from (irb):8
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>


上例僅僅是等同以下寫法而已:
a = 10
class Some
    def some
        puts a
    end
end
Some.new.some

然而,你可以使用define_method來動態建立實例方法。例如:
>> a = 10
=> 10
>> class Some; end
=> nil
>> Some.class_eval do
?>     define_method("some") { puts a }
>> end
=> #<Proc:0x25f7328@(irb):4 (lambda)>
>> Some.new.some
10
=> nil
>>


以上卻是標準class建立類別時無法作到的事情:
>> a = 10
=> 10
>> class Some
>>     define_method("some") { puts a }
>> end
=> #<Proc:0x25fec30@(irb):6 (lambda)>
>> Some.new.some
NameError: undefined local variable or method `a' for #<Some:0x25686d8>
        from (irb):6:in `block in <class:Some>'
        from (irb):8
        from C:/Winware/Ruby192/bin/irb:12:in `<main>'
>>