编写一个像incf这样的破坏性宏或函数?

3
我需要一个在增量期间进行边界检查的incf函数:
val := val + delta
if val >= 1.0
   then return 1.0
   else return val

我可以使用incf来编写这个代码:
(defun incf-bounded(val delta)
  (incf val delta)
  (if (>= val 1.0) 1.0 val))    

在这种情况下,我需要使用如下方式:(setf x (incf-bounded x delta))。但是,如果我想使用类似于(incf-bounded x delta) 的方式呢?也就是说,x将被修改。该怎么写呢?

1
这可能是 what is to append as push is to cons, in Lisp? 的重复,其中被接受的答案解释了 define-modify-macro 的使用,这很可能是你想在这里使用的。 - Joshua Taylor
你所编写的Lisp代码与你的伪代码不符。else行不应该是else val := val + delta吗? - Joshua Taylor
2个回答

5
这是一个很好的使用define-modify-macro的案例(在Lisp中,append类似于push和cons的区别是什么?中也有描述,但当前情况更简单)。首先,将您的有界和写成一个函数。这很简单;它接受valdelta,如果它们的总和大于1.0,则返回1.0,否则返回它们的总和。根据您发布的伪代码和Lisp代码,可以这样写:
(defun sum-bounded (val delta)
  (if (>= (+ val delta) 1.0)
      1.0
      (+ val delta)))

实际上,只需计算这个值,您可以使用:
(defun sum-bounded (val delta)
  (min 1.0 (+ val delta)))

现在您可以使用define-modify-macro来定义一个名为incf-bounded的宏:
(define-modify-macro incf-bounded (delta) sum-bounded)

该宏以一个“位置”作为第一个参数,以 delta 作为第二个参数。它安全地从该位置中检索值,使用该值和 delta 计算 sum-bounded,并将结果存储回该位置。“安全”在这里的意思是避免多次评估可能出现的问题,正如Lars Brinkhoff's所警告的那样。然后您只需使用它即可:
(let ((x .5))
  (incf-bounded x .3)
  (print x)                             ; prints 0.8
  (incf-bounded x .3)
  (print x))                            ; prints 1.0 (not 1.1)

如果更复杂的情况下,你想要修改的位置不是宏的第一个参数,那么你需要编写自己的宏并使用get-setf-expansion,但这在

代码全部放在一起以便复制和粘贴

(defun sum-bounded (val delta)
  "Returns the lesser of 1.0 or the sum of val and delta."
  (min 1.0 (+ val delta)))

(define-modify-macro incf-bounded (delta) sum-bounded
  "(incf-bounded place delta) computes the sum of the value of the
place and delta, and assigns the lesser of 1.0 and the sum of the value
and delta to place.")

(defun demo ()
  (let ((x .5))
    (incf-bounded x .3)
    (print x)                           ; prints 0.8
    (incf-bounded x .3)
    (print x)))                         ; prints 1.0 (not 1.1)

4

如果您想让val成为一个可以产生副作用的位置,那么您需要小心处理。

(defmacro incf-bounded (form delta &environment env)
  (multiple-value-bind (temps vals vars writer reader)
      (get-setf-expansion form env)
    `(let* (,@(mapcar #'list temps vals)
            (,(first vars) (min (+ ,delta ,reader) 1.0))) ;Edited, see comments.
       ,writer)))

例如,尝试使用以下内容:

(let ((list (list 0 0.5 1)))
  (loop with i = -1 repeat 3 do (incf-bounded (nth (incf i) list) 0.5))
  list)

这看起来过于复杂,因为我想在incf-bounded的第一个参数中产生副作用。


1
说到不必要的复杂性,为什么不直接使用define-modify-macro呢?它可以为您处理get-setf-expansion。 :) (无耻广告:我在https://dev59.com/4njZa4cB1Zd3GeqPazH6#19497981中放了一个例子。) - Joshua Taylor
可以的,那很方便。我个人非常喜欢使用get-setf-expension - Lars Brinkhoff
1
为了正常工作,(+ ,reader ,delta) 表达式应该改为 (+ ,delta ,reader) CLHS 5.1.3 基于 SETF 的其他宏的处理 - acelent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接