Написание макроса ++ в Common Lisp

Я пытался написать макрос Lisp, который будет выполнять эквивалент ++ на других языках программирования по семантическим причинам. Я попытался сделать это несколькими различными способами, но ни один из них не работает, и все они принимаются интерпретатором, поэтому я не знаю, есть ли у меня правильный синтаксис или нет. Мое представление о том, как это будет определено будет

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

но это дает мне ошибку простого типа при попытке ее использовать. Что заставит его работать?

8 ответов


помните, что макрос возвращает выражение для вычисления. Для того чтобы сделать это, вы должны надстрочного:

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

оба предыдущие ответы, но они дают вам макрос, который вы называете

(++ 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 на самом деле читать as (incf Вар.)


Я бы настоятельно не советовал делать псевдоним для 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: получить старое значение. В Common 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 может содержать код, специфичный для реализации. Это не значит, что наш макрос не переносится!)

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


семантически префиксные операторы ++ и -- на языке c++ или любом другом эквивалентны incf / decf в common lisp. Если вы это понимаете и, как и ваш (неправильный) макрос ,на самом деле ищете синтаксическое изменение, вам уже показали, как это сделать с помощью backticks, таких как `(incf, x). Вам даже показали, как заставить читателя взломать это, чтобы получить что-то ближе к синтаксису non-lisp. В том-то и загвоздка, что ни одна из этих вещей не является хорошей идеей. В целом, не идиоматическое кодирование, чтобы сделать язык более похожим на другой, просто не оказывается такой хорошей идеей.

однако, если вы действительно ищете семантику, у вас уже есть версии префиксов, как отмечено, но версии постфиксов не будут легко сопоставляться синтаксически. Вы могли бы сделать это с достаточным количеством читателя hackery, но это не будет красиво.

Если это то, что вы ищете, я бы предложил a) придерживаться имен incf / decf, поскольку они идиоматичны и хорошо работать и б) написать пост-incf, пост-decf версии, электронная.г (defmacro пост-incf (х) `(prog1 ,х (incf ,х)) разные вещи.

лично я не вижу, как это было бы особенно полезно, но ymmv.


для предварительного приращения уже есть incf, но вы можете определить свой собственный с

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

для post-increment вы можете использовать это (из 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+)

Altough я бы определенно иметь в виду замечания и хедз-ап, что Симон комментарии в своем посте, я действительно думаю, что user10029подход по-прежнему стоит попробовать, поэтому, просто для удовольствия, я попытался объединить его с принятым ответом, чтобы сделать ++x работа оператора (то есть приращение значения x в 1). Попробуйте!

объяснение: старый добрый SBCL не будет компилировать свою версию, потому что символ " + " должен быть явно задано в таблице поиска dispatch-char с 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| ' s строкой документации для примера использования. (Тесно) связанную документацию можно найти здесь:

эта реализация имеет как следствие, что количество записей, таких как +123, больше не понимается (отладчик переходит с no dispatch function defined for #\Newline) но дальнейшее обходное решение (или даже избегание) кажется разумным: если вы все еще хотите придерживаться этого, возможно, лучший выбор - не принимать ++ в качестве префикса, а ## или любой другой более DSL-ish решение

ура!

Андрес


это должно сделать трюк, однако я не гуру lisp.

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