在Common Lisp中编写++宏

时间:2020-03-05 18:54:14  来源:igfitidea点击:

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

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

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

解决方案

回答

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

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

回答

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

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

回答

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

(++ varname)

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

(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,但是我们可以使用

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

回答

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

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

如果这就是我们要查找的内容,我建议a)坚持使用incf / decf名称,因为它们是惯用的并且可以正常工作,并且b)编写inf-incf后-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)))