Написание макроса ++ в Common Lisp - PullRequest
6 голосов
/ 15 сентября 2008

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

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

но это дает мне ПРОСТУЮ ОШИБКУ ТИПА при попытке его использовать. Что бы это сработало?

Ответы [ 8 ]

17 голосов
/ 15 сентября 2008

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

(defmacro ++ (variable)
   `(incf ,variable))
12 голосов
/ 16 сентября 2008

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

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

9 голосов
/ 28 октября 2008

Я бы настоятельно рекомендовал не создавать псевдоним для 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)))
8 голосов
/ 13 мая 2012

Синтаксис (++ a) - бесполезный псевдоним для (incf a). Но предположим, что вам нужна семантика постинкремента: получить старое значение. В Common Lisp это делается с prog1, как в: (prog1 i (incf i)). Common Lisp не страдает от ненадежных или неоднозначных оценочных приказов. Предыдущее выражение означает, что i вычисляется, и значение сохраняется где-то, затем (incf i) оценивается, и затем возвращается сохраненное значение.

Создание полностью пуленепробиваемого pincf (post- incf) не совсем тривиально. (incf i) имеет свойство nice, которое 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 system::store. Обратите внимание, что этот вызов магазина использует временные переменные, а не исходные выражения A и I. (INCF I) появляется только один раз.

7 голосов
/ 17 сентября 2008

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

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

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

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

4 голосов
/ 17 сентября 2008

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

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

Для постинкремента вы можете использовать это (из тарифа утилит):

(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+)
1 голос
/ 20 марта 2016

Хотя я бы определенно учел замечания и комментарии, которые simon комментирует в своем посте, я действительно считаю, что подход 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 docstring для примера использования. (Тесно) связанную документацию можно найти здесь:

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

ура!

Andres

0 голосов
/ 15 сентября 2008

Это должно сработать, но я не гуру лиспа.

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