Jay*_*Jay 141 lisp scheme eval clojure common-lisp
我知道Lisp和Scheme程序员通常会说eval除非必要,否则应该避免.我已经看到了几种编程语言的相同建议,但我还没有看到一个反对使用的明确论据列表eval.我在哪里可以找到使用中潜在问题的说明eval?
例如,我知道GOTO程序编程中的问题(使程序难以理解且难以维护,难以找到安全问题等),但我从未见过反对的论点eval.
有趣的GOTO是,相反的论点应该对延续有效,但我看到Schemers,例如,不会说延续是"邪恶的" - 你在使用时应该小心.他们更倾向于使用代码而eval不是代码使用continuation(据我所知 - 我可能是错的).
Rai*_*wig 148
人们不应该使用的原因有几个EVAL.
初学者的主要原因是:你不需要它.
示例(假设Common Lisp):
使用不同的运算符评估表达式:
(let ((ops '(+ *)))
(dolist (op ops)
(print (eval (list op 1 2 3)))))
Run Code Online (Sandbox Code Playgroud)
那写得更好:
(let ((ops '(+ *)))
(dolist (op ops)
(print (funcall op 1 2 3))))
Run Code Online (Sandbox Code Playgroud)
有许多初学者学习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)
(list (if (eql a 3) 'sin 'cos) b))
Run Code Online (Sandbox Code Playgroud)
所以,我可能要编写基于第一参数使用无论是宏观SIN还是COS.
(foo 3 4)做(sin 4)和(foo 1 4)做(cos 4).
现在我们可能有:
(foo (+ 2 1) 4)
Run Code Online (Sandbox Code Playgroud)
这不会产生预期的结果.
然后可能想FOO通过EVALuating变量来修复宏:
(defmacro foo (a b)
(list (if (eql (eval a) 3) 'sin 'cos) b))
(foo (+ 2 1) 4)
Run Code Online (Sandbox Code Playgroud)
但是这仍然不起作用:
(defun bar (a b)
(foo a b))
Run Code Online (Sandbox Code Playgroud)
在编译时不知道变量的值.
一个避免的一般重要原因EVAL:它经常被用于丑陋的黑客攻击.
JUS*_*ION 41
eval(用任何语言)都不像电锯那样邪恶.这是一个工具.它恰好是一个强大的工具,当被误用时,可以切断肢体和剔骨(隐喻地说),但程序员工具箱中的许多工具也可以这样说,包括:
goto 和朋友如果你发现自己不得不使用任何这些功能强大的潜在危险工具,请问自己三次"为什么?" 在一个链.例如:
"为什么我要用
eval?" "因为foo." "为什么foo是必要的?" "因为..."
如果你到达该链的末尾并且该工具看起来仍然是正确的事情,那就去做吧.将地狱记录下来.测试地狱.一遍又一遍地仔细检查正确性和安全性.但是这样做.
Tor*_*amo 26
Eval很好,只要你完全知道它会发生什么.必须检查和验证进入它的任何用户输入以及所有内容.如果您不知道如何100%确定,那么不要这样做.
基本上,用户可以键入所讨论语言的任何代码,并且它将执行.你可以想象自己可以做多少伤害.
Dan*_*ark 14
国际海事组织,这个问题不是LISP特有的.以下是针对PHP的相同问题的答案,它适用于LISP,Ruby和其他具有eval的语言:
eval()的主要问题是:
- 潜在的不安全输入.传递不受信任的参数是一种失败的方法.确保参数(或其一部分)完全受信任通常不是一项微不足道的任务.
- Trickyness.使用eval()可以使代码更聪明,因此更难以遵循.引用Brian Kernighan" 调试的速度是编写代码的两倍.因此,如果您尽可能巧妙地编写代码,那么根据定义,您不够聪明,无法对其进行调试 "
实际使用eval()的主要问题只有一个:
- 缺乏经验的开发人员使用它而没有充分考虑.
取自这里.
我认为棘手的一点是令人惊讶的.对代码高尔夫和简洁代码的痴迷总是产生"聪明"代码(对于这些代码来说,eval是一个很好的工具).但是你应该编写你的代码以便于阅读,IMO,不要证明你是一个聪明的人而不是节省纸张(你无论如何都不会打印它).
然后在LISP中存在与运行eval的上下文相关的一些问题,因此不受信任的代码可以访问更多内容; 无论如何,这个问题似乎很常见.
stc*_*ang 12
有很多很棒的答案,但这是另一个来自Racket的实施者Matthew Flatt:
http://blog.racket-lang.org/2011/10/on-eval-in-dynamic-languages-generally.html
他提出了许多已经涵盖的观点,但有些人可能会发现他的观点很有趣.
简介:使用它的上下文会影响eval的结果,但程序员通常不会考虑它,从而导致意外的结果.
小智 11
规范的答案是远离.我觉得很奇怪,因为它是一个原始的,七个原始的(其他的是缺点,汽车,cdr,if,eq和引用),它远远地消耗了最少的使用和爱.
来自On Lisp:"通常,明确地调用eval就像在机场礼品店买东西一样.等到最后一刻,你必须为有限的二流商品支付高价."
那么我什么时候使用eval?一般的用法是通过评估在REPL中使用REPL (loop (print (eval (read)))).每个人都可以使用.
但是您也可以根据宏来定义函数,这些函数将在编译后通过将eval与backquote相结合来进行评估.你走
(eval `(macro ,arg0 ,arg1 ,arg2))))
Run Code Online (Sandbox Code Playgroud)
它会为你杀死上下文.
Swank(对于emacs史莱姆)充满了这些案例.它们看起来像这样:
(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))))
Run Code Online (Sandbox Code Playgroud)
我不认为这是一个肮脏的黑客.我自己一直用它来将宏重新集成到函数中.
小智 7
关于Lisp eval的另外几点: