(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
должно быть новым значением.В некоторых случаях это может привести к плохому стилю, но здесь это будет хорошо.