在Common Lisp中编写++宏

时间:2008-09-15 18:47:58

标签: macros lisp common-lisp

我一直在尝试编写一个Lisp宏,由于语义原因,它会在其他编程语言中表现出相当于++的值。我试图用几种不同的方式做到这一点,但它们似乎都没有用,并且所有都被解释器接受,所以我不知道我是否有正确的语法。我对如何定义它的想法是

(defmacro ++ (variable)
  (incf variable))

但是在尝试使用它时,这给了我一个简单的错误。什么会使它发挥作用?

8 个答案:

答案 0 :(得分:17)

请记住,宏返回要计算的表达式。为此,您必须反驳:

(defmacro ++ (variable)
   `(incf ,variable))

答案 1 :(得分:12)

之前的两个答案都有效,但它们会为您提供一个您称为

的宏
(++ varname)

而不是varname ++或++ varname,我怀疑你想要它。我不知道你是否能真正得到前者,但对于后者,你可以做一个读取宏。由于它是两个字符,因此调度宏可能是最好的。未经测试,因为我没有方便的运行lisp,但有类似的东西:

(defun plusplus-reader (stream subchar arg)
   (declare (ignore subchar arg))
   (list 'incf (read stream t nil t)))
(set-dispatch-macro-character #\+ #\+ #'plusplus-reader)

应该使++ var实际上读取为(incf var)。

答案 2 :(得分:9)

我强烈建议不要为incf制作别名。它会降低阅读代码的人的可读性,他们必须问自己“这是什么?它与incf有何不同?”

如果你想要一个简单的后增量,试试这个:

(defmacro post-inc (number &optional (delta 1))
  "Returns the current value of number, and afterwards increases it by delta (default 1)."
  (let ((value (gensym)))
    `(let ((,value ,number))
       (incf ,number ,delta)
       ,value)))

答案 3 :(得分:8)

语法(++ a)(incf a)的无用别名。但是假设你想要后增量的语义:检索旧值。在Common Lisp中,这是使用prog1完成的,如:(prog1 i (incf i))。 Common Lisp不会受到不可靠或模糊的评估订单的影响。前面的表达式意味着计算i,并将值隐藏在某处,然后计算(incf i),然后返回隐藏的值。

制作完全防弹的pincf(后incf)并非完全无足轻重。 (incf i)具有i仅评估一次的良好属性。我们希望(pincf i)也拥有该属性。因此简单的宏不足:

(defmacro pincf (place &optional (increment 1))
  `(prog1 ,place (incf ,place ,increment))

为了做到这一点,我们必须求助于Lisp的“赋值位置分析器”get-setf-expansion来获取允许我们的宏正确编译访问的材料:

(defmacro pincf (place-expression &optional (increment 1) &environment env)
  (multiple-value-bind (temp-syms val-forms
                        store-vars store-form access-form)
                        (get-setf-expansion place-expression env)
    (when (cdr store-vars)
      (error "pincf: sorry, cannot increment multiple-value place. extend me!"))
    `(multiple-value-bind (,@temp-syms) (values ,@val-forms)
       (let ((,(car store-vars) ,access-form))
         (prog1 ,(car store-vars)
                (incf ,(car store-vars) ,increment)
                ,store-form)))))

CLISP的一些测试。 (注意:依赖get-setf-expansion中的材料的扩展可能包含特定于实现的代码。这并不意味着我们的宏不可移植!)

8]> (macroexpand `(pincf simple))
(LET* ((#:VALUES-12672 (MULTIPLE-VALUE-LIST (VALUES))))
 (LET ((#:NEW-12671 SIMPLE))
  (PROG1 #:NEW-12671 (INCF #:NEW-12671 1) (SETQ SIMPLE #:NEW-12671)))) ;
T
[9]> (macroexpand `(pincf (fifth list)))
(LET*
 ((#:VALUES-12675 (MULTIPLE-VALUE-LIST (VALUES LIST)))
  (#:G12673 (POP #:VALUES-12675)))
 (LET ((#:G12674 (FIFTH #:G12673)))
  (PROG1 #:G12674 (INCF #:G12674 1)
   (SYSTEM::%RPLACA (CDDDDR #:G12673) #:G12674)))) ;
T
[10]> (macroexpand `(pincf (aref a 42)))
(LET*
 ((#:VALUES-12679 (MULTIPLE-VALUE-LIST (VALUES A 42)))
  (#:G12676 (POP #:VALUES-12679)) (#:G12677 (POP #:VALUES-12679)))
 (LET ((#:G12678 (AREF #:G12676 #:G12677)))
  (PROG1 #:G12678 (INCF #:G12678 1)
   (SYSTEM::STORE #:G12676 #:G12677 #:G12678)))) ;
T

现在这是一个关键的测试案例。这里,该地方包含副作用:(aref a (incf i))。这必须只评估一次!

[11]> (macroexpand `(pincf (aref a (incf i))))
(LET*
 ((#:VALUES-12683 (MULTIPLE-VALUE-LIST (VALUES A (INCF I))))
  (#:G12680 (POP #:VALUES-12683)) (#:G12681 (POP #:VALUES-12683)))
 (LET ((#:G12682 (AREF #:G12680 #:G12681)))
  (PROG1 #:G12682 (INCF #:G12682 1)
   (SYSTEM::STORE #:G12680 #:G12681 #:G12682)))) ;
T

首先发生的事情是评估A(INCF I),并成为临时变量#:G12680#:G12681。访问该数组,并在#:G12682中捕获该值。然后我们将PROG1保留为返回值。该值递增,并通过CLISP的system::store函数存储回数组位置。请注意,此商店调用使用临时变量,而不是原始表达式AI(INCF I)只出现一次。

答案 4 :(得分:7)

从语义上讲,前缀运算符++和 - 在c ++之类的语言中或者在普通的lisp中等同于incf / decf。如果你意识到这一点,并且像你的(不正确的)宏一样,实际上正在寻找一个语法变化,那么你已经展示了如何使用像`(incf,x)这样的反引号来实现它。您甚至已经向您展示了如何让读者解决这个问题,以便更接近非lisp语法。尽管如此,这些都不是一个好主意。一般来说,非语言编码使语言更接近另一种语言并不是一个好主意。

但是,如果实际上是在寻找语义,那么你已经有了前面提到的版本,但后缀版本在语法上并不容易匹配。你可以用足够的读者hackery做到这一点,但它不会很漂亮。

如果那就是你要找的东西,我建议a)坚持使用incf / decf名称,因为它们是惯用的并且运作良好b)编写post-incf,post-decf版本,例如(defmacro post-incf (x)`(prog1,x(incf,x))种类的东西。

就我个人而言,我看不出这对ymmv来说特别有用。

答案 5 :(得分:4)

对于预增量,已有incf,但您可以使用

定义自己的
(define-modify-macro my-incf () 1+)

对于后增量,你可以使用它(来自fare-utils):

(defmacro define-values-post-modify-macro (name val-vars lambda-list function)
 "Multiple-values variant on define-modify macro, to yield pre-modification values"
 (let ((env (gensym "ENV")))
   `(defmacro ,name (,@val-vars ,@lambda-list &environment ,env)
      (multiple-value-bind (vars vals store-vars writer-form reader-form)
          (get-setf-expansion `(values ,,@val-vars) ,env)
       (let ((val-temps (mapcar #'(lambda (temp) (gensym (symbol-name temp)))
                                 ',val-vars)))
          `(let* (,@(mapcar #'list vars vals)
                  ,@store-vars)
             (multiple-value-bind ,val-temps ,reader-form
               (multiple-value-setq ,store-vars
                 (,',function ,@val-temps ,,@lambda-list))
               ,writer-form
               (values ,@val-temps))))))))

(defmacro define-post-modify-macro (name lambda-list function)
 "Variant on define-modify-macro, to yield pre-modification values"
 `(define-values-post-modify-macro ,name (,(gensym)) ,lambda-list ,function))

(define-post-modify-macro post-incf () 1+)

答案 6 :(得分:1)

Altough我绝对会记住 simon 评论中的评论和单挑,我真的认为 user10029 的方法仍然存在值得一试,所以,为了好玩,我试着将它与接受的答案结合起来,使 ++ x 运算符工作(即,将x的值增加到1)。试一试!

解释:好老的SBCL不会编译他的版本,因为' +'必须在带有make-dispatch-macro-character的dispatch-char查找表上显式设置symbol,并且在评估之前仍需要宏来传递变量的名称。所以这应该做的工作:

(defmacro increment (variable)
  "The accepted answer"
  `(incf ,variable))

(make-dispatch-macro-character #\+) ; make the dispatcher grab '+'

(defun |inc-reader| (stream subchar arg)
  "sets ++<NUM> as an alias for (incf <NUM>).
   Example: (setf x 1233.56) =>1233.56
            ++x => 1234.56
            x => 1234.56"
   (declare (ignore subchar arg))
   (list 'increment (read stream t nil t)))

(set-dispatch-macro-character #\+ #\+ #'|inc-reader|)

有关用法示例,请参阅|inc-reader| docstring 。 (密切)相关的文档可以在这里找到:

这个实现的结果就是不再理解像+123这样的数字条目(调试器跳入no dispatch function defined for #\Newline)但是进一步的解决方法(甚至避免)似乎是合理的:如果你仍然想坚持这一点,也许最好的选择不是把++作为前缀,而是##或任何其他更多的DSL-ish解决方案

喝彩!

安德烈

答案 7 :(得分:-2)

这应该可以解决问题,但我不是一个lisp大师。

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))