Написание макроса ++ в 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 строкой документации для примера использования. (Тесно) связанную документацию можно найти здесь:
- http://clhs.lisp.se/Body/f_set__1.htm
- http://clhs.lisp.se/Body/f_mk_dis.htm#make-dispatch-macro-character
эта реализация имеет как следствие, что количество записей, таких как +123, больше не понимается (отладчик переходит с no dispatch function defined for #\Newline
) но дальнейшее обходное решение (или даже избегание) кажется разумным: если вы все еще хотите придерживаться этого, возможно, лучший выбор - не принимать ++ в качестве префикса, а ## или любой другой более DSL-ish решение
ура!
Андрес
это должно сделать трюк, однако я не гуру lisp.
(defmacro ++ (variable)
`(setq ,variable (+ ,variable 1)))