2008-09-15 8 views
6

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

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

, но это дает мне SIMPLE-TYPE-ERROR при попытке использовать его. Что бы это сработало?

+0

Не дубликат, но связанные с: [? Пишущие деструктивную макросъемки или функции, как INCF] (http://stackoverflow.com/q/19485964/1281433) –

ответ

17

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

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

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

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

Это не совсем работа, как и ожидалось во всех ситуациях. Как вы говорите, «переменная» дважды оценивается, а это не то, что пользователь ожидает, если выражение имеет побочные эффекты. . посмотрите, как ваш макрос расширяет этот вполне разумный вызов: (++ (isf some-vector (++ some-index))) –

+0

Это также не работает, если 'variable' - это что-то другое, кроме переменной (или символа макро), потому что 'setq' не работает с непеременными. Например, вы не можете делать '(++ (car list))'. –

12

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

(++ varname) 

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

(defun plusplus-reader (stream subchar arg) 
    (declare (ignore subchar arg)) 
    (list 'incf (read stream t nil t))) 
(set-dispatch-macro-character #\+ #\+ #'plusplus-reader) 

должен сделать ++ вар на самом деле чтения как (INCF вар).

4

Для преинкремента, есть уже 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+) 
7

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

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

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

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

+1

Упоминание 'prog1' само по себе достаточно для этого сообщения. Долгое время использовал CL, и забыл, что он существует давным-давно. – Mars

8

Я настоятельно советую против сделав псевдоним для 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))) 
+1

Это дважды оценивает «номер». [Ответ Каз] (http://stackoverflow.com/a/10567794/1281433) показывает, как этого избежать. –

8

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

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

+1

@JoshuaTaylor Макрос, созданный 'define-modify-macro', возвращает новое обновленное значение. Так как это то, что 'incf' нужно вернуть, это легко. Неясно, одинаково ли легко написать 'pincf' с' define-modify-macro', где требование - вернуть значение, которое было ранее на месте. – Kaz

1

Altough Я бы определенно иметь в виду замечания и головы вверх, что симон комментарии в своем посте, я действительно думаю, что user10029 «s подход все еще стоит попробовать, так, просто для удовольствия, я попытался объединить его с принятым ответом, чтобы сделать работу оператора ++ x (т. е. увеличить значение 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-ish решение

ура!

Andres