Ruby的evaluation是一个很重要的功能,它可以eval一个字符串或者一个block。在一些适宜的情况下使用它会得到一些“意外”的效果。
常用的eval
Ruby常用的evaluation有:class_eval (module_eval),instance_eval ,eval 。
这三种evaluation方法可以在不同的情况下使用:
1. class_eval
class_eval和module_eval是相同的,class_eval是module_eval的一个alias。
class_eval可以在一个mod的上下文中eval一个字符串或者一个block,常用于给一个类添加方法(实例方法):
- class Thing
- end
- a = %q{def hello() "Hello there!" end}
- Thing.module_eval(a)
- puts Thing.new.hello() #=> "Hello there!"
2. instance_eval
instance_eval可以在一个实例的上下文中eval一个字符串或者一个block:
- class Klass
- def initialize
- @secret = 99
- end
- end
- k = Klass.new
- k.instance_eval { @secret } #=> 99
3. eval
eval是在当前上下文中eval一个字符串,如果指定一个binding,则在binding的上下文中eval。
- def getBinding(str)
- return binding
- end
- str = "hello"
- eval "str + ' Fred'" #=> "hello Fred"
- eval "str + ' Fred'", getBinding("bye") #=> "bye Fred"
class_eval和instance_eval的不同
首先注意到class_eval和instance_eval的不同主要在于执行上下文(context)的不同。class_eval在一个mod的上下文中执行,而instance_eval在一个实例的上下文中执行。
其次,他们的常用场景不同。class_eval的应用场景一般是“打开一个类”来做一些事情,比如增加方法,或者是include一个module。
- String.class_eval do
- include ExtraMethods
- def another_method
- ....
- end
- end
而instance_eval主要关注于一个实例。
- class Paginator
- def initialize total_entires
- @total_entries = total_entires
- @page_index = 0
- end
- def next
- @page_index += 1
- end
- end
- paginator = Paginator.new(100)
- paginator.next
- paginator.instance_eval "@page_index" #=> 1
- paginator.instance_eval { @page_index } #=> 1
当然,我们也同样可以通过class_eval给Paginator类增加一个方法来获取page_index。
在我们了解instance_eval和class_eval之前,很可能会误用它们,比如通过下面的方法给Paginator增加一个实例方法:
- Paginator.instance_eval do
- def baz
- "baz"
- end
- end
结果:
- Paginator.new.baz #=> undefined method ‘baz’ for #<Foo:0x7dce8>
但我们偶然发现:
- Paginator.baz #=> "baz"
其实,如果我们了解ruby的对象模型,对这个结果并不会意外。我刚才已经说过:instance_eval关注于一个实例,它是在一个实例的上下文中执行的。Paginator类本身就是一个Class类的实例,所以Paginator.instance_eval做的就是给这个Class实例--Paginator--增加一个方法,也就是Paginator的类方法。
同样,我们可以通过instance_eval给任意类的实例增加方法,比如:
- "good".instance_eval do
- def opposite
- "bad"
- end
- end
- "good".opposite #=> "bad"
这个opposite方法就是"good"实例的一个singleton method。
instance_eval和DSL
DSL的一个主要特点是可以在不同的上下文中执行,下面就是一个例子(窃取自Jay Field举的例子):
- class SqlGenerator
- class << self
- def evaluate(&script)
- self.new.instance_eval(&script)
- end
- end
- def multiply(arg)
- "select #{arg}"
- end
- def two(arg=nil)
- "2#{arg}"
- end
- def times(arg)
- " * #{arg}"
- end
- end
- SqlGenerator.evaluate { multiply two times two }
- #=> "select 2 * 2"
- class Calculator
- class << self
- def evaluate(&script)
- self.new.instance_eval(&script)
- end
- end
- def multiply(arg)
- eval arg
- end
- def two(arg=nil)
- "2#{arg}"
- end
- def times(arg)
- " * #{arg}"
- end
- end
- Calculator.evaluate { multiply two times two }
- #=> 4
简单得介绍了一下ruby的evaluation,要了解更多,可以看下面这两篇文章:
http://www.infoq.com/articles/eval-options-in-ruby
http://blog.jayfields.com/2007/03/ruby-instanceeval-and-classeval-method.html