在Common Lisp中编写++宏

在Common Lisp中编写++宏

Writing a ++ macro in Common Lisp

我一直在尝试编写一个Lisp宏,出于语义上的原因,该宏在其他编程语言中将表现为++。 我尝试以几种不同的方式执行此操作,但是它们似乎都不起作用,并且都被解释器接受,所以我不知道我是否具有正确的语法。 我对如何定义的想法是

1
2
(defmacro ++ (variable)
  (incf variable))

但这在尝试使用它时给了我SIMPLE-TYPE-ERROR。 什么会使它起作用?


请记住,宏返回要求值的表达式。为此,您必须反引号:

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

前面的两个答案都可以,但是它们为您提供了一个宏,您将其称为

1
(++ varname)

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

1
2
3
4
(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有何不同?"的人,这都会降低可读性。

如果您想要一个简单的后增量,请尝试以下操作:

1
2
3
4
5
6
(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仅被评估一次。我们希望(pincf i)也具有该属性。因此,简单的宏不够用:

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

为此,我们必须求助于Lisp的"分配位置分析器",称为get-setf-expansion,以获得可以使我们的宏正确编译访问权限的材料:

1
2
3
4
5
6
7
8
9
10
11
(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的扩展可能包含特定于实现的代码。这并不意味着我们的宏不可移植!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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))。这必须被评估一次!

1
2
3
4
5
6
7
8
[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)仅出现一次。


从语义上讲,前缀运算符++和-用c ++之类的语言或常见lisp中等效的incf / decf表示。如果您意识到这一点,并且像(不正确的)宏一样,实际上正在寻找语法更改,那么您已经被展示了如何使用`(incf,x)这样的反引号来做到这一点。甚至还向您展示了如何使读者对此有所帮助,以使之更接近非lisp语法。不过这很麻烦,因为这些东西都不是一个好主意。通常,使一种语言更接近于另一种语言的非惯用编码并不是一个好主意。

但是,如果实际上正在寻找语义,您已经获得了前面提到的前缀版本,但是后缀版本在语法上很难匹配。您可以使用足够多的阅读器黑客来做到这一点,但这并不是一件很漂亮的事情。

如果这就是您要查找的内容,我建议a)坚持使用incf / decf名称,因为它们是惯用的并且可以正常工作; b)编写inf-incf后的版本,例如(defmacro post-incf(x) `(prog1,x(incf,x))之类的东西。

就个人而言,我不认为这特别有用,但ymmv。


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

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

对于后增量,您可以使用以下命令(来自fare-utils):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(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+)


尽管我一定会牢记simon在其帖子中的评论和注意,但我确实认为user10029的方法仍然值得一试,因此,为了好玩,我尝试将其与公认的答案结合起来, ++ x运算符的工作(即,将x的值增加1)。试试看!

说明:好的旧版SBCL不会编译其版本,因为必须在<-x0>上的dispatch-char查找表上显式设置'+'符号,并且在评估它之前仍然需要宏来传递变量的名称。因此,这应该可以完成工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(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|的文档字符串。 (密切相关的)文档可以在这里找到:

  • http://clhs.lisp.se/Body/f_set__1.htm
  • http://clhs.lisp.se/Body/f_mk_dis.htm#make-dispatch-macro-character

因此,此实现导致不再了解+123之类的数字条目(调试器使用no dispatch function defined for #
ewline
跳转),但进一步的解决方法(甚至避免)似乎是合理的:如果您仍然希望坚持使用此方法,也许最好的选择是不要以++为前缀,而是使用##或其他任何类似DSL的解决方案

干杯!

安德烈斯


这应该可以解决问题,但是我不是精打细算的专家。

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

推荐阅读

    linux分辨率操作命令?

    linux分辨率操作命令?,系统,情况,分辨率,底部,状态,命令,屏幕,屏幕分辨率,

    linux定时执行命令?

    linux定时执行命令?,时间,系统,服务,任务,工作,标准,情况,周期性,工具,命令,l

    linux看命令执行过程?

    linux看命令执行过程?,系统,服务,状态,软件,时间,数据,地址,命令,进程,情况,l

    linux登陆执行命令?

    linux登陆执行命令?,系统,服务,工具,地址,密码,百度,管理,检测,网络,第一,怎

    linux常见操作命令?

    linux常见操作命令?,系统,工作,信息,管理,地址,命令,目录,单位,数据,标准,lin

    linux下执行命令行?

    linux下执行命令行?,工作,系统,信息,单位,命令,基础,地址,设备,权威,标准,mv

    linux安装执行命令?

    linux安装执行命令?,系统,软件,网络,密码,官方网站,在线,工作,盘中,电脑,第

    linux执行命令超时?

    linux执行命令超时?,时间,代码,系统,名字,环境,工作,服务,下来,名称,地址,如

    linux远程执行多命令?

    linux远程执行多命令?,工具,服务,命令,状态,暂停,代码,底部,时间,地址,系统,L

    红帽子linux操作命令?

    红帽子linux操作命令?,服务,系统,密码,环境,信息,通用,软件,状态,设备,命令,

    linux命令行执行工具?

    linux命令行执行工具?,工具,系统,网络,分析,工作,服务,状态,信息,电脑,发行,s

    linux执行命令超时?

    linux执行命令超时?,时间,代码,系统,名字,环境,工作,服务,下来,名称,地址,如

    linux安装执行命令?

    linux安装执行命令?,系统,软件,网络,密码,官方网站,在线,工作,盘中,电脑,第

    linux执行两条命令?

    linux执行两条命令?,单位,工作,地址,命令,连续,系统,分行,权威,信息,目录,Lin

    linux执行pl命令?

    linux执行pl命令?,代码,服务,工具,位置,标准,系统,首页,数据,操纵,环境,perl

    linux命令执行10次?

    linux命令执行10次?,地址,工作,信息,系统,命令,目录,标准,设备,发行,文件,Lin

    linux执行命令后无号?

    linux执行命令后无号?,系统,环境,信息,工具,状态,数据,命令,文件,字符集,环

    linux日常操作命令?

    linux日常操作命令?,工作,系统,地址,信息,命令,目录,基础,管理,操作,功能,lin

    linux周期执行命令?

    linux周期执行命令?,工作,系统,周期,地址,命令,工具,信息,时间,任务,目录,lin

    linux执行命令被阻止?

    linux执行命令被阻止?,档案,系统,服务,网络,工具,在线,信息,基础,状态,命令,