Macros 写a++;公共Lisp中的宏

Macros 写a++;公共Lisp中的宏,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我一直在尝试编写一个Lisp宏,由于语义原因,它在其他编程语言中的性能相当于+。我尝试了几种不同的方法,但似乎都不起作用,而且都被解释器接受,所以我不知道我的语法是否正确。我对如何定义这一点的想法是 (defmacro ++ (variable) (incf variable)) 但在尝试使用它时,这给了我一个简单的类型错误。什么使它工作?请记住,宏返回要计算的表达式。为此,您必须反向报价: (defmacro ++ (variable) `(incf ,variable)) 这应

我一直在尝试编写一个Lisp宏,由于语义原因,它在其他编程语言中的性能相当于+。我尝试了几种不同的方法,但似乎都不起作用,而且都被解释器接受,所以我不知道我的语法是否正确。我对如何定义这一点的想法是

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

但在尝试使用它时,这给了我一个简单的类型错误。什么使它工作?

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

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

这应该可以做到,但是我不是一个口齿不清的大师

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

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

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

应该将++变量实际读取为(incf-var)。

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

(define-modify-macro my-incf () 1+)
对于增量后,您可以使用以下内容(来自票价UTIL):


语义上,像C++之类的语言中的前缀运算符++和-或者是普通LISP中的等效的IcF/DEF。如果您意识到这一点,并且像您的(不正确的)宏一样,实际上正在寻找语法更改,那么已经向您展示了如何使用像`(incf,x)这样的反勾号进行更改。您甚至已经了解了如何让读者绕过这一点,从而更接近非lisp语法。这就是问题所在,因为这两件事都不是一个好主意。一般来说,用非惯用编码使一种语言与另一种语言更相似并不是一个好主意

然而,如果你真的在寻找语义,你已经得到了前面提到的前缀版本,但是后缀版本在语法上并不容易匹配。你可以用足够多的读者黑客来做,但这并不漂亮

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


就个人而言,我不认为这会有什么特别的用处,但ymmv。

我强烈建议不要为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)))
语法
(++a)
对于
(incf a)
来说是一个无用的别名。但是假设您想要post increment的语义:检索旧值。在公共Lisp中,这是通过
prog1
完成的,如:
(prog1 i(incf i))
。Common Lisp不会遇到不可靠或不明确的求值顺序。前面的表达式表示求值
i
,并将值隐藏在某个位置,然后求值
(incf i)
,然后返回隐藏的值

制作一个完全防弹的
pincf
(post-
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
中材料的扩展可能包含特定于实现的代码。这并不意味着我们的宏不可移植!)

现在,这里是一个关键的测试用例。在这里,这个地方包含一个副作用:
(arefa(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
。然后我们的
程序1
保留该值以供返回。值被递增,并通过CLISP的
系统::s存储回数组位置tore
函数。请注意,此存储调用使用临时变量,而不是原始表达式
A
I
(INCF I)
只出现一次。

虽然我肯定会记住simon在其帖子中的评论并提醒大家,但我真的认为user10029的方法仍然值得一试,因此,为了好玩,我尝试将其与公认的答案结合起来,使++x操作符工作(即,在1中增加x的值).试试看

解释:好的老SBCL不会编译他的版本,因为必须使用
make dispatch macro character
在dispatch char查找表上显式设置“+”符号,并且在计算变量之前仍然需要宏传递变量的名称。因此,这应该可以完成以下工作:

(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|)
(defmacro增量(变量)
“公认的答案”
`(incf,可变)
(使分派宏字符#\+);使分派器抓取“+”
(defun | inc reader |)(流次字符arg)
“将++设置为(incf)的别名。
示例:(setf x 1233.56)=>1233.56
++x=>1234.56
x=>1234.56“
(声明(忽略子字符arg))
(列表“增量(读取流t nil t)))
(设置分派宏字符#\+\+\\+'| inc reader |)
有关用法示例,请参见
| inc reader |
的docstring。可以在此处找到(密切相关的)文档:

这个实现的结果是,+123这样的数字条目不再是und
[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
(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|)