论坛首页 编程语言技术论坛

Rails每周一题(十六): Evaluation in Ruby

浏览 3887 次
精华帖 (0) :: 良好帖 (7) :: 新手帖 (3) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-08-02   最后修改:2009-08-08

Ruby的evaluation是一个很重要的功能,它可以eval一个字符串或者一个block。在一些适宜的情况下使用它会得到一些“意外”的效果。

 

常用的eval

 

Ruby常用的evaluation有:class_eval (module_eval),instance_evaleval

 

这三种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

   发表时间:2009-08-03  
ruby for rails 这本书里介绍得比较详细
0 请登录后投票
   发表时间:2009-08-03  
原来
SqlGenerator.evaluate { multiply two times two }  

SqlGenerator.evaluate { multiply(two(times(two))) }  

的意思啊 ,看起来好神秘。。
0 请登录后投票
   发表时间:2009-08-03  
Hooopo 写道
原来
SqlGenerator.evaluate { multiply two times two }  

SqlGenerator.evaluate { multiply(two(times(two))) }  

的意思啊 ,看起来好神秘。。


可以把空格理解为右结合的运算符 ~


ruby 1.9 还有一种 eval 方法
is = RubyVM::InstructionSequence.compile('1+1')
is.eval


这个东西还能用来看 YARV 字节码:
pp is.to_a
puts is.disasm
0 请登录后投票
   发表时间:2009-08-03  
我觉得class_eval和instance_eval这两个名字起的有些晦涩,还有module_eval和class_eval是一个意思这也让人难以理解。。。
我觉得class_eval如果这样比较合理些:


class Thing
  
end

class Module
  def my_class_eval(*args)
    if block_given?
      yield
    else
      eval *args
    end
  end

end
str = %q{
  def hello
    puts "hello"
  end
}

Thing.my_class_eval(str)
Thing.my_class_eval do
  def say_hi
    puts "Hi~~"
  end
end
Thing.hello # => hello
Thing.send(:say_hi) # => Hi~~
#不知道为什么用block后成了private方法,只好用send了。。

0 请登录后投票
   发表时间:2009-08-04  
在 Thing 里头加一个 public 就行了 ……
Thing;  public;end


public、private 会传播是个陷阱 …… def 之前最好标一标

ps:class 和 module 会返回块内最后一条语句的值。
class T;  @@a = 3;end
a = class T;@@a;end #=> 3
0 请登录后投票
   发表时间:2010-05-18   最后修改:2010-05-18
>> Thing.my_class_eval do
?>  def say_hi
>>   puts 'hi---'
>>  end
>> end
=> nil
>> Thing.say_hi
hi---

为哈子我的还是public的呢?
>> "good".instance_eval do 
?>   def opposite
>>      "bad"
>>   end
>> end
=> nil
>> 
?> "good".opposite #=> "bad"
NoMethodError: undefined method `opposite' for "good":String
	from (irb):51
>> a='good'
=> "good"
>> a.instance_eval do 
?>  def ss
>>   'ss'
>>  end
>> end
=> nil
>> a.ss
=> "ss"

1.87下跑的。结果不太一样。
0 请登录后投票
   发表时间:2010-05-18  
eval的速度也是慢得可以
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics