Как предотвратить оценку формы в макросах lisp? - PullRequest
0 голосов
/ 08 июня 2018

Я пытаюсь создать простую заметку defun.Как я могу предотвратить оценку формы args в этом коде?

(defmacro defun/memo (name args &rest body)
  `(let ((memo (make-hash-table :test 'equalp)))
     (defun ,name ,args
       (if (gethash (loop for x in ,args collect x) memo)
           (gethash (loop for x in ,args collect x) memo)
           (let ((result (progn ,@body)))
             (setf (gethash (loop for x in ,args collect x) memo) result)
             result)))))

Ошибка:

; in: DEFUN ADD
;     (X Y)
; 
; caught STYLE-WARNING:
;   undefined function: X
; 
; compilation unit finished
;   Undefined function:
;     X

1 Ответ

0 голосов
/ 08 июня 2018
(defmacro defun/memo (name args &rest body)

Обычно вы объявляете тело с &body body, а не &rest body.

Перехват переменных

  `(let ((memo (make-hash-table :test 'equalp)))

Символ memo заканчивается в сгенерированном коде,Если body содержит ссылки на memo, например, символ, который был лексически связан с вызовом defun/memo, он будет использовать вашу переменную.Вместо этого вы должны использовать свежий символ, сгенерированный внутри макроса с gensym (вне обратных кавычек).Например, вы могли бы сделать следующее, чтобы избежать оценки expr дважды:

(let ((var-expr (gensym)))
  `(let ((,var-expr ,expr))
     (+ ,var-expr ,var-expr)))

Loop

       (if (gethash (loop for x in ,args collect x) memo)
           (gethash (loop for x in ,args collect x) memo)
           (let ((result (progn ,@body)))
             (setf (gethash (loop for x in ,args collect x) memo) result)
             result)))))

Что должно делать следующее?

(loop for x in ,args collect x)

Допустим, вы определили функцию с (defun/memo test (a b c) ...), вы вставите буквальный список аргументов в приведенном выше, что приведет к коду, который содержит:

(loop for x in (a b c) collect x)

Как вы видели, код теперьпытаясь вызвать функцию a с аргументами b и c.

Что, если в вашем макросе вы указали args?

(loop for x in ',args collect x)

Тогда вы получите:

(loop for x in '(a b c) collect x)

А теперь вы просто копируете буквальный список.Когда сгенерированный выше код будет запущен, он создаст только новый список (a b c).Это то, что вам нужно?

Вам нужно взять все аргументы вашей функции, то есть список значений, которые вам дали.Цикл может быть заменен на:

(list ,@args)

Что будет расширяться как:

(list a b c)

И здесь у вас есть все ваши значения в списке.

Но общееLisp уже предоставляет способ получить все аргументы в виде списка:

(defun foo (&rest args) 
  ;; args is bound to a list of values
)

Ваша сгенерированная функция может сделать то же самое.

Gethash

Также, (if (gethash ...) (gethash ...) other) может бытьнаписано (or (gethash ...) other).Это дает преимущество оценки вызова на gethash только один раз.

Что еще важнее (спасибо @Sylwester), так как вы пишете универсальный макрос, вы не можете знать заранее, если nil будет возможным возвращаемым значением.Если значение равно nil, результат будет пересчитываться каждый раз, учитывая, как написано if / or.Вам нужно использовать вторичное возвращаемое значение из gethash, чтобы проверить, существует ли элемент:

(multiple-value-bind (value exists-p) (gethash ...)
  (if exists-p
      value
      (setf (gethash ...) ...)))

Кроме того, если ваша кэшированная функция возвращает несколько значений, вы можете получить их все с помощью multiple-value-list ивозвращает их с values-list.

Setf

Кстати, следующий код:

(let ((result expr))
  (setf place result)
  result)

... имеет мало причин не записываться как:

(setf place expr)

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

...