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

时间:2013-10-21 03:48:39

标签: macros lisp common-lisp

我需要一个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将被修改的位置?

2 个答案:

答案 0 :(得分:5)

这是define-modify-macro的一个很好的用例(what is to append as push is to cons, in Lisp?中也对此进行了描述,但本案例更简单)。首先,将有界和作为函数编写。这非常简单;它需要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)

宏将 place 作为其第一个参数,delta作为第二个参数。它安全地从该位置检索值,使用该值和delta计算sum-bounded,然后将结果存储回位置。这里的“安全”意味着它避免了多重评估可能出现的问题,如Lars Brinkhoff's wisely warns against。然后你就可以使用它了:

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

对于更复杂的情况, place 将被修改的 place 自然不是您想要的宏的第一个参数,您需要编写自己的宏并使用{{3} },但这在

中有更详细的解释

一起编码以便于复制和放大粘贴

(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)

答案 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的第一个参数中产生副作用。)