为什么eval被认为是“邪恶”的?

143
我知道Lisp和Scheme程序员通常会说,除非绝对必要,否则应避免使用eval。我看到过几种编程语言的相同建议,但我还没有看到一份明确反对使用eval的论据清单。在哪里可以找到关于使用eval的潜在问题的说明呢?
例如,我知道在过程式编程中使用GOTO会带来问题(使程序难以阅读和维护,使安全问题难以发现等),但我从未见过反对eval使用的论据。
有趣的是,与GOTO使用的相同论点也适用于continuations,但我看到Schemers不会说continuations是“邪恶的”--你只需要在使用它们时小心。他们更可能对使用eval的代码表示不满,而不是对使用continuations的代码(就我所见--我可能错了)。

5
"eval不是邪恶的,但它所执行的代码可能是邪恶的。" - Anurag
9
我认为你的评论体现了一种非常单一分派对象中心的世界观。这在大多数编程语言中可能是有效的,但在Common Lisp中则不同,其中方法不属于类,而在Clojure中则更为不同,因为类仅通过Java互操作函数进行支持。Jay将此问题标记为Scheme,它没有任何内置的类或方法概念(各种形式的面向对象编程可作为库使用)。 - Zak
3
@Zak,你是正确的,我只懂我知道的语言,但即使你在使用Word文档而没有使用样式,你也不是DRY。我的观点是利用技术不重复自己。 OO不是普遍适用的,这是真的... - Dan Rosenstark
4
我已经擅自给这个问题加上了Clojure标签,因为我认为Clojure用户可能会从这里发布的优秀答案中受益。 - Michał Marczyk
...对于Clojure而言,至少还有一个额外的原因:您将失去与ClojureScript及其衍生版本的兼容性。 - Charles Duffy
2
“goto”是“邪恶”的,因为它是一种变异形式:实际上,一个新值会突然地被分配给指令指针。Continuations不涉及变异;纯函数语言可以使用continuations。它们比诸如if和while这样的控制结构更加纯粹,尽管Dijkstra认为if和while只是对goto和标签的轻微语法糖而已。 - Kaz
12个回答

2

Eval不是邪恶的。Eval也不复杂。它是一个编译你传递给它的列表的函数。在大多数其他语言中,编译任意代码意味着需要学习语言的AST,并深入挖掘编译器内部以找出编译器API。在lisp中,你只需要调用eval。

何时使用它?每当你需要编译一些东西时,通常是一个在运行时接受、生成或修改任意代码的程序。

什么时候不应该使用它?所有其他情况。

为什么在不需要时不应该使用它?因为你会以一种不必要复杂的方式做某事,这可能会导致可读性、性能和调试问题。

但是如果我是初学者,我怎么知道是否应该使用它?始终尝试使用函数来实现所需内容。如果这行不通,添加宏。如果这仍然不起作用,那么就使用eval!

遵循这些规则,你就不会通过eval做坏事 :)


0

我非常喜欢Zak's answer,他已经抓住了问题的本质:eval用于编写新语言、脚本或修改语言。他没有进一步解释,所以我来举个例子:

(eval (read-line))

在这个简单的Lisp程序中,提示用户输入,然后对他们输入的任何内容进行评估。为了使其工作,如果程序被编译,则必须存在所有符号定义集,因为您不知道用户可能输入哪些函数,所以必须将它们全部包含进去。这意味着,如果您编译这个简单的程序,生成的二进制文件将非常庞大。
从原则上讲,由于这个原因,您甚至不能将其视为可编译语句。一般来说,一旦使用eval,就是在一个解释环境中操作,代码就无法再被编译了。如果您不使用eval,那么可以像C程序一样编译Lisp或Scheme程序。因此,在决定使用eval之前,您需要确保您想要和需要处于解释环境中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接