Macros SBCL中奇怪的宏扩展错误

Macros SBCL中奇怪的宏扩展错误,macros,lisp,common-lisp,sbcl,quote,Macros,Lisp,Common Lisp,Sbcl,Quote,我想使用SBCL 1.3.3 for Windows x86_64在Lisp中编写一个Fibonacci数计算函数fib。使用延迟计算来避免重复。目前的工作守则是: (defvar *fibs* (make-hash-table)) (defun get-value (idx) (if (functionp (gethash idx *fibs*)) (setf (gethash idx *fibs*) (funcall (gethash idx *fibs*)))

我想使用SBCL 1.3.3 for Windows x86_64在Lisp中编写一个Fibonacci数计算函数
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)))
但它说:

; 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
的宏扩展,并且
code for
作为符号(?)而不是数字接收
I
,这就是问题所在。尽管如此,我仍然不知道为什么代码是错误的,或者如何改进它

2018年4月12日编辑

正如tfb所指出的,解决问题的最佳方法取决于问题是什么。整个问题是向学生解释什么是懒惰评估,如何用Lisp进行评估,以及为什么有必要这样做。斐波那契数不是本例的主要目标

coredump显示了问题的根本原因(宏扩展)和解决方案(i的额外绑定)。不幸的是,这使得所有代码都不适合showcase,因为有太多额外的解释。因此,我最终通过递归更改
循环

(defvar*fibs*(生成哈希表))
(定义获取值(idx)
(if(functionp(gethash idx*fibs*))
(setf(gethash idx*fibs*)
(funcall(gethash idx*fibs*))
(gethash idx*fibs*))
(除纤(n和可选)(i(-n 1)))
(如果(
[注意coredump的回答解释了宏的错误:我重点讨论了函数的错误以及解决问题的更好方法。]

我不知道你为什么认为你需要这里的
EVAL
,或者宏:你可以用
LAMBDA
创建一个函数。以下是您的代码的一个有效版本:

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

(defun get-value (idx &optional (default nil))
  ;; return the value and whether it was there.  If it's a function,
  ;; call it and stash the result
  (multiple-value-bind (got presentp)
      (gethash idx *fibs* default)
    (values
     (typecase got
       (function (setf (gethash idx *fibs*)
                       (funcall got)))
       (t got))
     presentp)))

(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*)
                      (let ((i i))
                        ;; rebind I as we don't want to depend on whatever
                        ;; LOOP does, which probably is mutate a single
                        ;; binding of I
                        (lambda () (+ (get-value (- i 2))
                                      (get-value (- i 1)))))))
  ;; just return the first value as we know the second will be T, and
  ;; it's not interesting
  (values (get-value (- n 1))))
(defvar*fibs*(生成哈希表))
(defun获取值(idx&可选(默认为零))
;返回值以及它是否存在。如果它是函数,
打电话把结果藏起来
(多值绑定(got presentp)
(gethash idx*fibs*默认值)
(价值观
(打字机)
(函数(setf(gethash idx*fibs*))
(所有人都得到了)
(t得到)
演示文稿(P)))
(除纤维(n)
(从n以下0开始的i循环)
if(
但这是一个相当糟糕的方法。相反,您可以只使用显式记忆功能,如下所示:

(defun fibonacci (n)
  ;; an explicitly-memoized version of the Fibonacci function
  (let ((memo (make-hash-table :test #'eql)))
    (labels ((fib (m)
               (cond
                ((< m 1)
                 (error "defined on naturals (excluding 0)"))
                ((< m 3)
                 1)
                (t
                 (multiple-value-bind (v p) (gethash m memo)
                   (if p
                       v
                     (setf (gethash m memo) (+ (fib (- m 1))
                                                (fib (- m 2))))))))))
      (fib n))))
(定义斐波那契(n)
;斐波那契函数的明确记忆版本
(let((备忘录(制作哈希表:test#'eql)))
标签((fib(m)
(续)
(

更好的办法是,定义一个宏,让您可以记忆任何函数:有一些软件包可以让您这样做,尽管我不确定它们是什么(一个是由于我,但我不确定是否没有更好的,或者实际上是现在找到我的软件包的正确位置!)

[请注意,coredump的回答解释了宏的错误:我重点介绍了函数的错误以及解决问题的更好方法。]

我不知道你为什么认为你需要这里的
EVAL
,或者宏:你可以用
LAMBDA
创建一个函数

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

(defun get-value (idx &optional (default nil))
  ;; return the value and whether it was there.  If it's a function,
  ;; call it and stash the result
  (multiple-value-bind (got presentp)
      (gethash idx *fibs* default)
    (values
     (typecase got
       (function (setf (gethash idx *fibs*)
                       (funcall got)))
       (t got))
     presentp)))

(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*)
                      (let ((i i))
                        ;; rebind I as we don't want to depend on whatever
                        ;; LOOP does, which probably is mutate a single
                        ;; binding of I
                        (lambda () (+ (get-value (- i 2))
                                      (get-value (- i 1)))))))
  ;; just return the first value as we know the second will be T, and
  ;; it's not interesting
  (values (get-value (- n 1))))
(defvar*fibs*(生成哈希表))
(defun获取值(idx&可选(默认为零))
;返回值以及它是否存在。如果它是函数,
打电话把结果藏起来
(多值绑定(got presentp)
(gethash idx*fibs*默认值)
(价值观
(打字机)
(函数(setf(gethash idx*fibs*))
(所有人都得到了)
(t得到)
演示文稿(P)))
(除纤维(n)
(从n以下0开始的i循环)
if(
但这是一种相当糟糕的方法。相反,您可以只使用一个显式记忆函数,如下所示:

(defun fibonacci (n)
  ;; an explicitly-memoized version of the Fibonacci function
  (let ((memo (make-hash-table :test #'eql)))
    (labels ((fib (m)
               (cond
                ((< m 1)
                 (error "defined on naturals (excluding 0)"))
                ((< m 3)
                 1)
                (t
                 (multiple-value-bind (v p) (gethash m memo)
                   (if p
                       v
                     (setf (gethash m memo) (+ (fib (- m 1))
                                                (fib (- m 2))))))))))
      (fib n))))
(定义斐波那契(n)
;斐波那契函数的明确记忆版本
(let((备忘录(制作哈希表:test#'eql)))
标签((fib(m)
(续)
((code-for i)
(lambda () (+ (get-value (- i 2))
              (get-value (- i 1))))
(let ((i i)) (lambda ...))