不应该使用EVAL
的原因有几个。
对于初学者来说,主要原因是:你不需要它。
例如(假设是Common Lisp):
使用不同操作符计算表达式:
(let ((ops '(+ *)))
(dolist (op ops)
(print (eval (list op 1 2 3)))))
这可以更好地书写为:
(let ((ops '(+ *)))
(dolist (op ops)
(print (funcall op 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
开启了安全问题
- 使用
EVAL
进行某些求值可能发生在错误的时间并创建构建问题。
以下是一个简化的例子来解释最后一点:
(defmacro foo (a b)
(list (if (eql a 3) 'sin 'cos) b))
因此,我可能想编写一个宏,根据第一个参数使用 SIN
或 COS
。
(foo 3 4)
执行 (sin 4)
,而 (foo 1 4)
执行 (cos 4)
。
现在我们可能有:
(foo (+ 2 1) 4)
这样做并不能得到期望的结果。
此时,您可能希望通过对变量进行EVAL操作来修复宏FOO
:
(defmacro foo (a b)
(list (if (eql (eval a) 3) 'sin 'cos) b))
(foo (+ 2 1) 4)
但是这仍然不起作用:
(defun bar (a b)
(foo a b))
变量的值在编译时是未知的。
避免使用 EVAL
的一个普遍重要原因:它经常被用于丑陋的 hack。