Вопрос

Я пытался написать макрос 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.Это ухудшит читабельность для всех, кто читает ваш код и задается вопросом: «Что это?чем он отличается от 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 оценивается только один раз.Мы хотели бы (pincf i) тоже иметь это свойство.Итак, простой макрос не соответствует действительности:

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

Чтобы сделать это правильно, нам придется прибегнуть к «анализатору места назначения» Лиспа, называемому 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++ или любом другом языке эквивалентны incf/decf в common lisp.Если вы это понимаете и, как и ваш (неправильный) макрос, на самом деле ищете синтаксическое изменение, то вам уже показали, как это сделать с помощью обратных кавычек, таких как `(incf ,x).Вам даже показали, как заставить читателя обойти это, чтобы получить что-то ближе к синтаксису, отличному от Lisp.Однако в этом и есть загвоздка, поскольку ни одна из этих вещей не является хорошей идеей.В общем, неидиоматическое кодирование, чтобы сделать язык более похожим на другой, оказывается не такой уж хорошей идеей.

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

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

Лично я не понимаю, чем это было бы особенно полезно, но ymmv.

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

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

Для пост-инкремента вы можете использовать это (из тарифа-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+)

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

Объяснение:Старый добрый SBCL не стал бы компилировать свою версию, потому что символ «+» должен быть явно установлен в таблице поиска диспетчерских символов с помощью 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.

ваше здоровье!

Андрес

Это должно помочь, однако я не гуру шепелявости.

(defmacro ++ (variable)
  `(setq ,variable (+ ,variable 1)))
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top