eval 魔法

eval 方法提供了一种执行字符串的 Ruby 表达式的简单方法。乍一看,eval 可能看起来与双引号字符串中的 #{} 标记限定表达式完全相同。以下两行代码产生相同的结果:

eval.rb
  1. puts( eval("1 + 2" ) )
  2. puts( "#{1 + 2}" )

但是,有时候结果可能不是你所期望的。请看以下内容,例如:

eval_string.rb
  1. exp = gets().chomp()
  2. puts( eval( exp ))
  3. puts( "#{exp}" )

假设你输入 2 * 4 并将其分配给 exp。当你使用 eval 计算 exp 时,结果为 8,但是当你在双引号字符串中计算 exp 时,结果为 ‘2 * 4’。这是因为 gets() 读入的任何内容都是字符串,"#{exp}" 将其作为字符串而不是表达式进行计算,而 eval(exp) 将字符串作为表达式求值。

为了强制在字符串中进行求值,你可以在字符串中放置 eval(尽管如此,可能会偏离我们的目标):

  1. puts( "#{eval(exp)}" )

这是另一个例子。尝试一下,并在出现提示时按照说明操作:

eval2.rb
  1. print( "Enter the name of a string method (e.g. reverse or upcase): " ) # user enters: upcase
  2. methodname = gets().chomp()
  3. exp2 = "'Hello world'."<< methodname
  4. puts( eval( exp2 ) ) #=> HELLO WORLD
  5. puts( "#{exp2}" ) #=> "Hello world".upcase
  6. puts( "#{eval(exp2)}" ) #=> HELLO WORLD

eval 方法可以执行计算跨越多行的字符串,从而可以执行嵌入字符串中的整个程序:

eval3.rb
  1. eval( 'def aMethod( x )
  2. return( x * 2 )
  3. end
  4. num = 100
  5. puts( "This is the result of the calculation:" )
  6. puts( aMethod( num ))' )

有了所有这些 eval 的能力,现在让我们看看编写一个它自己可以编写程序的程序是多么容易。这里:

eval4.rb
  1. input = ""
  2. until input == "q"
  3. input = gets().chomp()
  4. if input != "q" then eval( input ) end
  5. end

这可能看起来不多,但是这个程序允许你从命令提示符中创建和执行真正可用的 Ruby 代码。试试看。运行程序并一次一行地输入这两个方法(但是不要点 ‘q’ 来退出 - 我们稍后会写一些代码):

  1. def x(aStr); puts(aStr.upcase);end
  2. def y(aStr); puts(aStr.reverse);end

请注意,你必须在一行中输入每个整个方法代码,因为我的程序在输入时按行执行。我将在后面解释如何解决这个限制。归功于 eval,每个方法都变成了真实可行的 Ruby 代码。你可以通过输入以下内容来证明这一点:

  1. x("hello world")
  2. y("hello world")

现在,这些表达式本身已被执行,它们将调用我们刚刚编写的两个方法,从而产生以下输出:

  1. HELLO WORLD
  2. dlrow olleh

仅仅五行代码很不错了!