Clojure 为什么eval是邪恶的?

Clojure 为什么eval是邪恶的?,clojure,scheme,lisp,common-lisp,eval,Clojure,Scheme,Lisp,Common Lisp,Eval,我知道Lisp和Scheme程序员通常说除非严格必要,否则应该避免使用eval。我已经看到了对几种编程语言的相同建议,但我还没有看到反对使用eval的明确论据列表。在哪里可以找到有关使用eval的潜在问题的说明 例如,我知道过程编程中的GOTO问题(使程序无法阅读和维护,使安全问题难以发现等),但我从未见过反对eval的论点 有趣的是,反对GOTO的相同论据应该对continuations有效,但我看到,例如,阴谋家不会说continuations是“邪恶的”——在使用它们时应该小心。他们更可能

我知道Lisp和Scheme程序员通常说除非严格必要,否则应该避免使用
eval
。我已经看到了对几种编程语言的相同建议,但我还没有看到反对使用
eval
的明确论据列表。在哪里可以找到有关使用
eval
的潜在问题的说明

例如,我知道过程编程中的
GOTO
问题(使程序无法阅读和维护,使安全问题难以发现等),但我从未见过反对
eval
的论点


有趣的是,反对
GOTO
的相同论据应该对continuations有效,但我看到,例如,阴谋家不会说continuations是“邪恶的”——在使用它们时应该小心。他们更可能不喜欢使用
eval
的代码,而不喜欢使用continuations的代码(据我所知,我可能是错的)。

只要你确切地知道eval是怎么回事。任何进入其中的用户输入都必须经过检查和验证。如果你不知道如何百分之百地确定,那就不要这样做

基本上,用户可以为所讨论的语言键入任何代码,然后执行。你可以自己想象他能造成多大的伤害。

就像后藤“规则”:如果你不知道自己在做什么,你会把事情搞得一团糟

除了从已知的安全数据中构建一些东西之外,还有一个问题,即某些语言/实现无法充分优化代码。您可能最终会在
eval

中找到解释过的代码,eval是不安全的。 例如,您有以下代码:

eval('
hello('.$_GET['user'].');
');
现在用户来到您的站点并输入url)$是_admin=true;回音(

然后生成的代码将是:

hello();$is_admin=true;echo();
eval
(在任何语言中)都不是邪恶的,就像电锯不是邪恶的一样。它是一种工具。它恰巧是一种强大的工具,当被误用时,可以切断四肢并取出内脏(隐喻地说),但程序员工具箱中的许多工具也是如此,包括:

  • 转到
    和朋友
  • 基于锁的线程
  • 延续
  • 宏(卫生或其他)
  • 指针
  • 可重启异常
  • 自修改代码
  • …还有成千上万的演员
如果你发现自己不得不使用这些功能强大、潜在危险的工具,那么在一个链条中问自己三次“为什么”。例如:

“为什么我必须使用
eval
?” “因为福。”“为什么是福?” “有必要吗?”“因为……”


如果你走到了这条链的尽头,而这个工具看起来仍然是正确的,那么就去做。记录它。测试它。反复检查正确性和安全性。但是去做。

有几个理由不应该使用
EVAL

初学者的主要原因是:你不需要它。

示例(假设公共Lisp):

使用不同的运算符计算表达式:

(let((ops'(+*))
(精神科医生(行动)
(打印(评估(列表op 1 2 3(()))))
最好写为:

(let((ops'(+*))
(精神科医生(行动)
(打印(所有操作1 2 3)))
在很多例子中,学习Lisp的初学者认为他们需要
EVAL
,但他们不需要它-因为表达式是经过计算的,人们也可以计算函数部分。大多数情况下,使用
EVAL
表示对计算器缺乏了解

宏也有同样的问题。通常初学者编写宏,他们应该在那里编写函数——不理解宏的真正用途,也不理解函数已经完成了这项工作

对于作业来说,使用
EVAL
通常是错误的工具,这通常表明初学者不理解通常的Lisp求值规则

如果您认为需要
EVAL
,请检查是否可以使用类似
FUNCALL
REDUCE
APPLY
的内容

  • FUNCALL
    -使用参数调用函数:
    (FUNCALL'+1 2 3)
  • REDUCE
    -调用值列表上的函数并合并结果:
    (REDUCE'+'(1 2 3))
  • APPLY
    -使用列表作为参数调用函数:
    (APPLY'+'(1 2 3))
问:我真的需要eval,还是编译器/计算器已经满足了我的需求

对于稍高级的用户,避免使用
EVAL
的主要原因如下:

  • 您希望确保您的代码已编译,因为编译器可以检查代码中的许多问题,并生成更快的代码,有时甚至更快(这是因子1000;-)的代码

  • 无法尽早编译已构造并需要评估的代码

  • 评估任意用户输入会带来安全问题

  • 使用
    EVAL
    进行评估可能会在错误的时间发生,并造成构建问题

用一个简化的例子来解释最后一点:

(defmacro-foo(a-b)
(列表(如果(eql a 3)“sin”cos)b))
因此,我可能想编写一个基于第一个参数的宏,它使用
SIN
COS

(foo34)
(sin4)
(foo14)
(cos4)

现在我们可能有:

(foo (+ 2 1) 4)
这并不能得到期望的结果

然后可能需要通过计算变量来修复宏
FOO

(defmacro-foo(a-b)
(列表(如果(eql(评估a)3)“sin”cos)b))
(富(+21)4)
但这仍然不起作用:

(defun bar (a b)
  (foo a b))
变量的值仅为
(eval `(macro ,arg0 ,arg1 ,arg2))))
(defun toggle-trace-aux (fspec &rest args)
  (cond ((member fspec (eval '(trace)) :test #'equal)
         (eval `(untrace ,fspec))
         (format nil "~S is now untraced." fspec))
        (t
         (eval `(trace ,@(if args `(:encapsulate nil) (list)) ,fspec ,@args))
         (format nil "~S is now traced." fspec))))
(eval (read-line))