создание макроса для повторения в Common Lisp - PullRequest
0 голосов
/ 30 октября 2018

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

(iterate i 1 5 1 (print (list 'one i)))

(где i - управляющая переменная, 1 - начальное значение, 5 - конечное значение и 1 - значение приращения). Я получаю SETQ: variable X has no value

 (defmacro += (x y)
        (list 'setf x '(+ x y)))


(defmacro iterate (control beginExp endExp incExp &rest body)
    (let ( (end (gensym)) (inc (gensym)))
        `(do ( (,control ,beginExp (+= ,control ,inc)) (,end ,endExp) (,inc ,incExp) )
            ( (> ,control ,end) T)
            ,@ body
        )
    )
)

Я пробовал несколько разных вещей, чтобы исправить это, связавшись с ,, и эта ошибка не позволяет мне понять, связана ли проблема с iterate или +=. Из того, что я могу сказать += работает нормально.

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Проверьте расширение +=, чтобы найти ошибку

Вам необходимо проверить расширение:

CL-USER 3 > (defmacro += (x y)
              (list 'setf x '(+ x y)))
+=

CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T

Расширение макроса выше показывает, что используются x и y, что является ошибкой. Нам нужно оценить их внутри функции макроса:

CL-USER 5 > (defmacro += (x y)
              (list 'setf x (list '+ x y)))
+=

CL-USER 6 > (macroexpand-1 '(+= a 1))
(SETF A (+ A 1))
T

Выше выглядит лучше. Примечание, кстати. что макрос уже существует в стандартном Common Lisp. Это называется incf.

Обратите внимание, что вам это не нужно, потому что побочный эффект не требуется в вашем коде iterate. Мы можем просто использовать функцию + без установки какой-либо переменной.

Style

Возможно, вы захотите настроить немного под стиль Лисп:

  • no camelCase -> считыватель по умолчанию не учитывает регистр символов
  • говорящих имен переменных -> улучшает читаемость
  • строка документации в макросе / функции - улучшает читаемость
  • GENSYM принимает строку аргумента -> улучшает читаемость сгенерированного кода
  • без висячих скобок и без пробелов между скобками -> делает код более компактным
  • лучше и автоматический отступ -> улучшает читаемость
  • тело помечено &body, а не &rest -> улучшает автоматическое отступление макрокоманд с помощью iterate
  • do не требуется макрос += для обновления итерационной переменной, поскольку do обновляет саму переменную -> побочных эффектов не требуется, нам нужно только вычислить следующее значение
  • Как правило, для написания хорошего макроса требуется немного больше времени, чем для написания нормальной функции, потому что мы программируем на мета-уровне с генерацией кода, и есть еще о чем подумать и несколько основных подводных камней. Итак, не торопитесь, перечитайте код, проверьте расширения, напишите некоторую документацию, ...

Применительно к вашему коду оно теперь выглядит так:

(defmacro iterate (variable start end step &body body)
  "Iterates VARIABLE from START to END by STEP.
For each step the BODY gets executed."
  (let ((end-variable  (gensym "END"))
        (step-variable (gensym "STEP")))
    `(do ((,variable ,start (+ ,variable ,step-variable))
          (,end-variable ,end)
          (,step-variable ,step))
         ((> ,variable ,end-variable) t)
       ,@body)))

В Лиспе первая часть - переменная, начало, конец, шаг - обычно записывается в виде списка. См. Например DOTIMES. Это позволяет, например, сделать step необязательным и присвоить ему значение по умолчанию:

(defmacro iterate ((variable start end &optional (step 1)) &body body)
  "Iterates VARIABLE from START to END by STEP.
For each step the BODY gets executed."
  (let ((end-variable  (gensym "END"))
        (step-variable (gensym "STEP")))
    `(do ((,variable ,start (+ ,variable ,step-variable))
          (,end-variable ,end)
          (,step-variable ,step))
         ((> ,variable ,end-variable) t)
       ,@body)))

Посмотрим расширение, отформатированное для удобства чтения. Мы используем функцию macroexpand-1, которая выполняет расширение макроса только один раз, а не расширение макроса сгенерированного кода.

CL-USER 10 > (macroexpand-1 '(iterate (i 1 10 2)
                               (print i)
                               (print (* i 2))))
(DO ((I 1 (+ I #:STEP2864))
     (#:END2863 10)
     (#:STEP2864 2))
    ((> I #:END2863) T)
  (PRINT I)
  (PRINT (* I 2)))
T

Вы можете видеть, что символы, созданные gensym, также могут быть идентифицированы по их имени.

Мы также можем позволить Лиспу отформатировать сгенерированный код, используя функцию pprint и указав правильное поле.

CL-USER 18 > (let ((*print-right-margin* 40))
               (pprint
                (macroexpand-1
                 '(iterate (i 1 10 2)
                    (print i)
                    (print (* i 2))))))

(DO ((I 1 (+ I #:STEP2905))
     (#:END2904 10)
     (#:STEP2905 2))
    ((> I #:END2904) T)
  (PRINT I)
  (PRINT (* I 2)))
0 голосов
/ 30 октября 2018

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

 (defmacro += (x y)
        `(setf ,x (+ ,x ,y)))


(defmacro iterate2 (control beginExpr endExpr incrExpr &rest bodyExpr)
    (let ((incr(gensym))(end(gensym)) )
        `(do ((,incr ,incrExpr)(,end ,endExpr)(,control ,beginExpr(+= ,control ,incr)))
            ((> ,control ,end) T)
            ,@ bodyExpr
        )
    )

)
...