什么时候Ruby中的'eval`是合理的?

时间:2009-12-14 18:48:47

标签: ruby eval

Is 'eval' supposed to be nasty?”激发了这一点:

大多数人都认为eval很糟糕,而且在大多数情况下会有更优雅/更安全的替代品。

所以我想问:如果eval经常被误用,是否真的需要作为语言功能?它是在做更坏的而不是好事吗?

就个人而言,我发现它唯一有用的地方是插入配置文件中提供的字符串。

编辑:这个问题的目的是在eval是唯一或最佳解决方案时尽可能多地获得真实案例。所以,请不要进入“如果语言限制程序员的创造力”的方向。

Edit2:当我说eval时,我当然是指eval字符串,而不是将红宝石块传递给instance_evalclass_eval

10 个答案:

答案 0 :(得分:23)

我所知道的唯一情况(除了“我有这个字符串,我想执行它”)是动态处理本地和全局变量。 Ruby有方法来获取局部变量和全局变量的名称,但是缺少基于这些名称来获取或设置其值的方法。进行AFAIK的唯一方法是使用eval

任何其他用途几乎肯定是错误的。我不是大师,也不能明确说明没有其他人,但是我曾经见过的其他一些用例,有人说“你需要eval,”我找到了一个没有的解决方案。 / p>

请注意,我在这里谈论 string eval 。顺便说一下。 Ruby也有instance_eval,可以在接收器的上下文中执行字符串或块。这种方法的块形式快速,安全且非常有用。

答案 1 :(得分:15)

什么时候有道理?我会说什么时候没有合理的选择。我能够想到一个我无法想到替代方案的用法:irb,如果你深入挖掘(workspace.rb,我的副本中的第80行,如果你感兴趣的话)使用eval执行你的输入:

def evaluate(context, statements, file = __FILE__, line = __LINE__)
  eval(statements, @binding, file, line)
end

这对我来说似乎很合理 - 在你被要求这样做的那一刻,你特别不知道你要执行什么代码的情况。动态和互动的东西似乎符合要求。

答案 2 :(得分:9)

eval存在的原因是因为当你需要它时,当你真正需要它时,没有替代品。毕竟,只有你可以用创造性的方法调度做这么多,并且在某些时候你需要执行任意代码。

仅仅因为语言具有可能危险的功能并不意味着它本身就是一件坏事。当一种语言假设比其用户更多时,那就是遇到麻烦的时候。

我认为当你发现一种没有危险的编程语言时,你会找到一种不太有用的编程语言。

eval何时合理?用务实的话来说,当你说的时候。如果它是你的程序并且你是程序员,你可以设置参数。

答案 3 :(得分:6)

eval()有一个非常重要的用例,它不能使用任何其他东西来实现(AFAIK),那就是为绑定找到相应的对象引用。

假设您已经传递了一个块但是(由于某种原因)您需要访问绑定的对象上下文,您将执行以下操作:

obj = eval('self', block.binding)

定义以下内容也很有用:

class Proc
    def __context__
        eval('self', self.binding)
    end
end

答案 4 :(得分:5)

IMO主要针对领域特定语言。

Evaluation Options in Ruby”是Jay Fields关于InfoQ的文章。

答案 5 :(得分:3)

eval是一种工具,它本身既不好也不邪恶。只要你确定它是你想要完成的工具的正确工具,这是合理的。

答案 6 :(得分:2)

像eval这样的工具是关于在运行时评估代码与“编译”时间。你知道启动Ruby时的代码是什么吗?那么你可能不需要eval。您的代码是否在运行时生成代码?那么你可能需要评估它。

例如,递归代码解析器中所需的方法/函数取决于要解析的语言。如果您的应用程序在运行中构建这样的解析器,那么使用eval可能是有意义的。你可以编写一个通用的解析器,但它可能不是一个优雅的解决方案。

Programatically filling in a letrec in Scheme. Macros or eval?”是我在Scheme中发布的一个关于eval的问题,其使用大多是不可避免的。

答案 7 :(得分:2)

一般情况下,eval是一种非常有用的语言功能,可以运行任意代码。这应该是一件罕见的事情,但也许您正在制作自己的REPL,或者您希望由于某种原因将ruby运行时暴露给最终用户。它可能发生,这就是该功能存在的原因。如果您使用它来解决语言的某些部分(例如全局变量),那么语言是有缺陷的,或者您对语言的理解是有缺陷的。解决方案通常不使用eval,而是要更好地理解语言或选择不同的语言。

值得注意的是,在ruby中,instance_evalclass_eval还有其他用途。

答案 8 :(得分:0)

您很可能在没有意识到的情况下经常使用 eval;这就是 ruby​​gems 如何加载 Gemspec 的内容。通过rubygems/lib/specification.rb

  # Note: I've removed some lines from that listing to illustrate the core concept
  
  def self.load(file)
   
    code = File.read(file)
    begin
      _spec = eval code, binding, file       #  <--------  EVAL HAPPENS HERE 

      if Gem::Specification === _spec
        return _spec
      end

      warn "[#{file}] isn't a Gem::Specification (#{_spec.class} instead)."
    rescue SignalException, SystemExit
      raise
    rescue SyntaxError, Exception => e
      warn "Invalid gemspec in [#{file}]: #{e}"
    end

    nil
  end

通常,gem specification would look like this

Gem::Specification.new do |s|
  s.name        = 'example'
  s.version     = '0.1.0'
  s.licenses    = ['MIT']
  s.summary     = "This is an example!"
  s.description = "Much longer explanation of the example!"
  s.authors     = ["Ruby Coder"]
  s.email       = 'rubycoder@example.com'
  s.files       = ["lib/example.rb"]
  s.homepage    = 'https://rubygems.org/gems/example'
  s.metadata    = { "source_code_uri" => "https://github.com/example/example" }
end

请注意,gemspec 文件只是创建一个新对象,但不会分配它,也不会将其发送到任何地方。 尝试 loadrequire 这个文件(甚至用 Ruby 执行它)不会返回 Gem::Specification 值。 eval 是提取由外部 ruby​​ 文件定义的的唯一方法。

答案 9 :(得分:0)

eval 的一个用途是将另一种语言编译为 ruby​​:

ruby_code = "(def foo (f a b) (mapv f (cons a b)))".compile_to_ruby
# "foo_proc = ->(f a b) { mapv_proc.call(f, (cons_proc.call(a, b)) }"
eval ruby_code