【发布时间】:2018-04-11 12:25:28
【问题描述】:
我想使用适用于 Windows x86_64 的 SBCL 1.3.3 在 Lisp 中编写一个斐波那契数计算函数 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*)))
(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)))
现在,我不想在fib 内部调用eval,所以我在这里介绍宏:
(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总是用作整数。
经过一番研究,我发现在宏 loop 中存在 code-for 的宏扩展,并且 code-for 将 i 接收为符号 (?) 而不是数字,这就是抱怨。不过,我不知道为什么代码是错误的,或者如何改进它。
编辑 12.04.2018。
正如 tfb 所指出的,解决问题的最佳方法取决于问题所在。整个问题是向学生解释什么是惰性评估,如何在 Lisp 中完成它以及为什么它可能是必要的。斐波那契数不是本示例的主要目标。
coredump 显示了问题的根本原因(宏扩展)及其解决方案(i 的额外绑定)。不幸的是,由于过多的额外解释,这使得所有代码都不适合展示。所以我最终通过递归更改loop:
(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))))
【问题讨论】:
-
如果这是试图解释惰性求值,那么这不是一个很好的例子,因为它混淆了惰性求值和记忆化,这根本不是一回事:如果你写一个
fib函数一种惰性语言天真地具有与急切语言相同的糟糕性能。 -
fib的惰性版本的性能优于它的渴望版本。在渴望版本(+ (fib (- n 2)) (fib (- n 1)))中,由于对fib的两次递归调用,复杂性呈指数级增长。在惰性版本(+ (get-value (- n 2)) (get-value (- n 1)))中,复杂性是线性的,因为只有对get-value的第一次调用实际计算了一些东西,第二次使用已经计算的值。
标签: macros lisp common-lisp sbcl quote