Arith-eval(普通的 lisp:一个温和的介绍)

Arith-eval (common lisp: a gentle introduction)

我正在学习Common Lisp: a gentle introduction,想解决所有的问题。

有时我有不同的解决方案。看得我一头雾水,看不懂书上的标准答案

例如 arith-eval:

我的解决方案是:

(defun arith-eval (x)
  (cond
    ((atom x) x)
    (t (eval (cons (cadr x) 
               (cons (car x)
                 (list (arith-eval (caddr x)))))))))

书中的解法:

(defun arith-eval (exp)
  (cond ((numberp exp) exp)
        (t (funcall (second exp)
             (arith-eval (first exp))
             (arith-eval (third exp))))))

遇到这种情况我该怎么办?

让我们从“evalfuncall 是非常不同的野兽”开始。

eval 函数接受一个 S 表达式并在 "null lexical environment" 中计算它(本质上,只有动态变量是可见的)。 funcall 函数采用函数指示符(函数对象或具有函数绑定的符号)和 0 个或多个参数。按照正常的函数,参数在当前词法环境中计算。

在一般情况下,我建议不要使用 eval 除非你绝对需要,funcallapply 几乎总是解决此类问题的正确工具。

您的解决方案 a) 不正确 b) 使用了错误的方法。

正确性

您的函数支持 (1 + (2 + 3)) 等表达式,但不支持 ((1 + 2) + 3)

CL-USER 6 > (arith-eval '((3 * 5) + 1))

Error: Illegal car 3 in compound form (3 * 5).

当您编写这样的解决方案时,您需要考虑可能的算术表达式是什么以及您的代码是否可以计算出解决方案。此外,考虑有用的测试用例和 运行 它们是一个好主意:

CL-USER 14 > (defparameter *test-cases*
               '( ( ((1 + 2) + 3) . 6)
                  ( (1 + 2 + 3)   . 6)
                  ( (1 + (2 + 3)) . 6)))
*TEST-CASES*

CL-USER 15 > (loop for (test . result) in *test-cases*
                   collect (list (ignore-errors (eql (arith-eval test)
                                                     result))
                                 test))
((NIL ((1 + 2) + 3))    ; failed
 (NIL (1 + 2 + 3))      ; failed, but probably not required
 (T   (1 + (2 + 3))))

接近

您的代码创建了一个 Lisp 形式,然后调用 eval,并以递归的方式进行。

解Lisp习题的第一条规则:不要使用EVAL

有更好的方法:

  • 由于表达式中的运算符符号已经是一个有效的 Lisp 函数,因此只需调用它并提供正确的参数即可。我们可以利用内置评估并通过递归调用 arith-eval 来计算参数。这就是书上的解决方法。

仍然可能有 eval 有意义的解决方案:

  • 将整个表达式从中缀转为前缀一次,然后调用eval(这里eval才有意义)

类似于(eval (infix-to-prefix expression))

现在必须编写函数 infix-to-prefix