我一直在尝试编写一个Lisp宏,它会出于语义原因而在其他编程语言中使用相当于++的++.我试图用几种不同的方式做到这一点,但它们似乎都没有用,并且所有都被解释器接受,所以我不知道我是否有正确的语法.我对如何定义它的想法是
(defmacro ++ (variable) (incf variable))
但是在尝试使用它时,这给了我一个简单的错误.什么会使它工作?
请记住,宏返回要计算的表达式.为了做到这一点,你必须反驳:
(defmacro ++ (variable) `(incf ,variable))
之前的两个答案都有效,但它们会为您提供一个您称之为的宏
(++ 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).
我强烈建议不要为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)
.但是假设你想要后增量的语义:检索旧值.在Common Lisp的,这与实现prog1
,如:(prog1 i (incf i))
.Common Lisp不会受到不可靠或模糊的评估订单的影响.前面的表达式意味着要i
进行求值,并将值隐藏在某处,然后(incf i)
进行求值,然后返回隐藏的值.
制作完全防弹pincf
(后期incf
)并非完全无足轻重.(incf i)
具有i
仅评估一次的nice属性.我们也希望(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
函数存储回数组位置.需要注意的是这家店调用使用临时变量,而不是原始表达式A
和I
. (INCF I)
只出现一次.
从语义上讲,前缀运算符++和 - 在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.