代码之家  ›  专栏  ›  技术社区  ›  Cristián Romo

用commonlisp编写++宏

  •  6
  • Cristián Romo  · 技术社区  · 17 年前

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

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

    8 回复  |  直到 12 年前
        1
  •  17
  •   acj    12 年前

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

    (defmacro ++ (variable)
       `(incf ,variable))
    
        2
  •  13
  •   Martijn Pieters    17 年前

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

    阅读

        3
  •  11
  •   Kaz    12 年前

    语法 (++ a) 是一个无用的别名 (incf a) . 但是假设您需要post increment的语义:检索旧值。在公共Lisp中,这是通过 prog1 (prog1 i (incf i)) . CommonLisp不会受到不可靠或不明确的求值命令的影响。前面的表达式表示 i (incf i) 计算,然后返回隐藏的值。

    做一个完全防弹的 pincf incf (incf i) 有一个很好的特性 (pincf i) 也要有那份财产。因此,简单的宏没有做到:

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

    要做到这一点,我们必须求助于Lisp的“分配位置分析器”,称为 get-setf-expansion 要获取允许宏正确编译access的材料,请执行以下操作:

    (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进行一些测试。(注:扩展依赖于来自 可能包含特定于实现的代码。这并不意味着我们的宏不可移植!)

    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 . 只出现一次。

        4
  •  10
  •   Svante    17 年前

    如果需要简单的后期增量,请尝试以下操作:

    (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)))
    
        5
  •  7
  •   simon    17 年前

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

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

        6
  •  5
  •   Kyle Cronin    17 年前

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

    对于增量后,您可以使用以下内容(来自票价UTIL):

    (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+)
    
        7
  •  2
  •   fr_andres    10 年前

    虽然我肯定会记住这些话,并提醒大家 西蒙 在他的帖子评论中,我真的认为 用户10029 的方法仍然值得一试,因此,为了好玩,我尝试将其与公认的答案结合起来,以使 ++x 操作员工作(即,在1中增加x的值)。试试看!

    解释 :Good old SBCL不会编译其版本,因为必须在dispatch char lookup表上使用显式设置“+”符号 make-dispatch-macro-character ,并且仍然需要宏在计算变量之前传递变量的名称。因此,这应该起到作用:

    (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| 文档字符串 有关用法示例,请参见。(密切)相关的文件可在此处找到:

    此实现的结果是不再理解像+123这样的数字条目(调试器使用 no dispatch function defined for #\Newline

    干杯

        8
  •  -2
  •   Drew Olson    17 年前

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

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