Arith-eval(常见的lisp:温和的介绍)

时间:2015-11-19 09:05:25

标签: lisp common-lisp

我正在学习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))))))

在这种情况下我该怎么办?

2 个答案:

答案 0 :(得分:7)

您的解决方案是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

答案 1 :(得分:3)

让我们从&#34开始; evalfuncall是非常不同的野兽&#34;。

eval函数采用S表达式并在&#34; null词法环境中对其进行评估&#34; (基本上,只有动态变量可见)。 funcall函数采用函数指示符(函数对象或具有函数绑定的符号)和0或更多参数。按照常规函数,参数在当前词法环境中进行评估。

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