在 DO 运行之前调用 EVAL 在 DO 的结果部分进行评估

Call to EVAL being evaluated in result portion of DO before DO runs

以下代码旨在从指定的等待时间开始倒计时,然后评估提供的表格:

(defun wait (seconds form)
       (let ((end (+ (get-universal-time) seconds)))
         (do ()
          ((>= (get-universal-time) end)
           (eval form))
        (sleep 1))))

如果我运行:

(wait 5 (format t "output"))

结果是 "output" 会在倒计时之前发送到 stdout。输出"output"后程序还是照常倒计时

我得到了预期的结果,其中 "output" 在倒计时完成后发送到标准输出,代码如下:

(defun wait (seconds form)
       (let ((end (+ (get-universal-time) seconds)))
         (do ()
          ((>= (get-universal-time) end)
           (format t "output"))
        (sleep 1))))

为什么在声明 DO 循环时在 DO 循环中对 EVAL 的调用正在计算,但直接插入正在计算的表单导致它等待到结果时间?

初学者Lisp编程第一定律:不,你不需要eval.

您的函数没有获得形式 (foo),而是计算 (foo) 的结果。在调用函数之前评估函数的所有参数。 Lisp 不使用参数的形式调用函数,而是使用评估参数的结果。

您的代码

(wait                   ; function wait
  5                     ; argument expression 1
  (format t "output"))  ; argument expression 2

会发生什么?

  1. wait是一个函数,搞定。
  2. 评估 5 -> 5
  3. 评估(format t "output") -> NIL + 作为副作用输出
  4. 使用参数 5NIL
  5. 调用函数 wait

改进:传递一个函数

如果您不想 运行 调用中的参数,请创建一个函数 (lambda () (foo)),它将被计算为一个函数对象,将其传递给一个变量 delayed-function, 并用 (funcall delayed-function).

调用它

这里发生了什么?

(wait
  5
  (lambda ()
    (format t "output")))
  1. wait是一个函数,搞定。
  2. 评估 5 -> 5
  3. 评估(lambda () (format t "output")) -> 函数对象
  4. 使用参数 5 和函数对象
  5. 调用函数 wait

现在你的函数 wait 需要做它想做的事并在正确的地方调用传递的函数对象 - 使用 FUNCALL.

当你调用一个函数时,它的参数在传递给函数之前会被评估一次。如果你想传递未计算的表单,你可以使用宏。例如:

(defmacro wait (seconds form)
  (let ((end-name (gensym "end")))
    `(do ((,end-name (+ (get-universal-time) ,seconds)))
         ((>= (get-universal-time) ,end-name))
       ,form
       (sleep 1))))

查看其宏展开:

CL-USER> (macroexpand-1 '(wait 10 (print 'test)))
(DO ((#:|end868| (+ (GET-UNIVERSAL-TIME) 10)))
    ((>= (GET-UNIVERSAL-TIME) #:|end868|))
  (PRINT 'TEST)
  (SLEEP 1))