我想用Lisp语言编写一个Fibonacci数字计算函数fib,它使用的是Windows x86的SBCL 1.3.3使用延迟计算以避免重复目前的工作准则是:

(defvar *fibs* (make-hash-table))

(defun get-value (idx)
  (if (functionp (gethash idx *fibs*))
    (setf (gethash idx *fibs*)
      (funcall (gethash idx *fibs*)))
    (gethash idx *fibs*)))

(defun fib (n)
  (loop for i from 0 below n
    if (< i 2) do (setf (gethash i *fibs*) 1)
    else do (setf (gethash i *fibs*)
      (eval `(lambda () (+ (get-value ,(- i 2))
                           (get-value ,(- i 1)))))))
  (get-value (- n 1)))

现在,我不想在eval内部调用fib,所以我在这里介绍宏:
(defvar *fibs* (make-hash-table))

(defun get-value (idx)
  (if (functionp (gethash idx *fibs*))
    (setf (gethash idx *fibs*)
      (funcall (gethash idx *fibs*)))
    (gethash idx *fibs*)))

(defmacro code-for (idx)
  `(lambda () (+ (get-value ,(- idx 2))
                 (get-value ,(- idx 1)))))

(defun fib (n)
  (loop for i from 0 below n
    if (< i 2) do (setf (gethash i *fibs*) 1)
    else do (setf (gethash i *fibs*) (code-for i)))
  (get-value (- n 1)))

但上面说:
; in: DEFUN FIB
;     (CODE-FOR I)
;
; caught ERROR:
;   during macroexpansion of (CODE-FOR I). Use *BREAK-ON-SIGNALS* to intercept.
;
;    Argument X is not a NUMBER: I
;
; compilation unit finished
;   caught 1 ERROR condition

很奇怪:我在代码中没有参数X,而I总是用作整数。
经过研究,我发现在宏中存在着code-for的宏扩展,并且loop作为符号(而不是作为一个数字,这就是抱怨但是,我不知道为什么代码是错误的,也不知道如何改进它。
编辑日期:2018年4月12日。
正如tfb所指出的,解决问题的最佳方法取决于问题是什么整个问题是要向学生解释什么是懒惰评价,如何用Lisp来做,以及为什么有必要这样做斐波那契数不是这个例子的主要目标。
coredump显示了问题的根本原因(宏扩展)及其解决方案(i的额外绑定)不幸的是,这使得所有代码都不适合showcase,因为有太多额外的解释所以我最后通过递归来改变code-for
(defvar *fibs* (make-hash-table))

(defun get-value (idx)
  (if (functionp (gethash idx *fibs*))
    (setf (gethash idx *fibs*)
      (funcall (gethash idx *fibs*)))
    (gethash idx *fibs*)))

(defun fib (n &optional (i (- n 1)))
  (if (< i 2) (setf (gethash i *fibs*) 1)
    (setf (gethash i *fibs*)
      (lambda () (+ (get-value (- i 2))
                    (get-value (- i 1))))))
  (if (zerop i) (get-value (- n 1))
    (fib n (- i 1))))

最佳答案

宏失败的原因
以下是code-for看到的:

(code-for i)

第一个参数是符号i但是,您正在尝试在宏扩展期间使用此符号执行算术运算这失败了。
现在,我不想在fib中调用eval,所以我引入了宏
这通常是宏的错误用法所有宏操作都是代码,无法知道运行时值等于多少。
不需要eval或宏
您可以轻松构建一个闭包:
(lambda () (+ (get-value (- i 2))
              (get-value (- i 1))))

这里唯一的问题是lambda结束的i被循环改变当您最终调用闭包时,它的值将不同你必须建立一个新的约束:
(let ((i i)) (lambda ...))

关于macros - SBCL中出现奇怪的宏扩展错误,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49775107/

10-09 17:06