Генерация произвольно параметризованных функций в цикле - PullRequest
0 голосов
/ 26 сентября 2018

Я пытаюсь создать кучу функций печенья и вставить их в хеш.Пока у меня есть макрос, который расширяется до такой функции:

(defmacro make-canned-format-macro (template field-names)
  `(function (lambda ,field-names
               (apply #'format `(nil ,,template ,,@field-names)))))

(defparameter *cookie-cutter-functions* (make-hash-table))

(setf (gethash 'zoom-zoom *cookie-cutter-functions*)
      (make-canned-format-macro "~A-powered ~A" (fuel device)))
(setf (gethash 'delicious *cookie-cutter-functions*)
      (make-canned-format-macro "~A ice cream" (flavor)))
(setf (gethash 'movie-ad *cookie-cutter-functions*)
      (make-canned-format-macro "~A: A ~A and ~A film" (title star co-star)))

Этот повторяющийся шаблон setf, gethash, make-canned-format-macro ужасно шаблонен, поэтому я попытался преобразовать егов цикл:

(loop
  for template in '(('zoom-zoom "~A-powered ~A" (fuel device))
                    ('delicious "~A ice cream" (flavor))
                    ('thematic "~A: A ~A and ~A film" (title star co-star)))
  do (let ((func-name (car template))
           (format-string (cadr template))
           (params (caddr template)))
        (setf (gethash func-name *cookie-cutter-functions*)
              (make-canned-format-macro format-string params))))

К сожалению, это взрывается, потому что make-canned-format-macro работает со значением PARAMS вместо значения OF paramsпотому что он расширяется макросом во время компиляции, а не оценивается во время выполнения.Но, как я узнал, когда я задал этот вопрос , make-canned-format-macro не будет работать как функция, потому что ему нужно создать форму lambda во время времени компиляции .(По крайней мере, я думаю, что это то, чему я научился из этого? Пожалуйста, скажите мне, что я ошибаюсь в этом вопросе! Я бы хотел, чтобы моя фабрика функций была функцией, а не макросом! )

Моя текущая мысль - написать макрос turn-this-list-of-templates-into-make-canned-format-macro-forms вместо цикла.Это то, что нужно делать (или, по крайней мере, не безумие), или есть лучший способ?

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Поскольку вы знаете аргументы во время компиляции / расширения макроса, вам не нужно применять:

CL-USER 35 > (defmacro make-canned-format-macro (template field-names)
               `(function (lambda ,field-names
                            (format nil ,template ,@field-names))))
MAKE-CANNED-FORMAT-MACRO

CL-USER 36 > (macroexpand-1 '(make-canned-format-macro "~A-powered ~A" (fuel device)))
(FUNCTION (LAMBDA (FUEL DEVICE) (FORMAT NIL "~A-powered ~A" FUEL DEVICE)))
T

Также нет необходимости заключать в кавычки в списке:

'('(a))

Такой код очень необычен.

Генерация кода во время выполнения

Имя -macro не имеет смысла, поскольку оно выполняет функцию.Функция должна генерировать исполняемый код: либо используйте EVAL, либо используйте COMPILE.

CL-USER 56 > (defun make-canned-format-function (template field-names)
               (compile nil `(lambda ,field-names
                               (format nil ,template ,@field-names))))
MAKE-CANNED-FORMAT-FUNCTION


CL-USER 57 > (loop
              for (func-name format-string params)
              in '((zoom-zoom "~A-powered ~A"        (fuel device))
                   (delicious "~A ice cream"         (flavor))
                   (thematic  "~A: A ~A and ~A film" (title star co-star)))
              do (setf (gethash func-name *cookie-cutter-functions*)
                       (make-canned-format-function format-string params)))
NIL

Построение с помощью макросов

CL-USER 77 > (defun make-canned-format-function-code (template fields)
               `(lambda ,fields
                  (format nil ,template ,@fields)))
MAKE-CANNED-FORMAT-FUNCTION-CODE

CL-USER 78 > (defmacro def-canned-format-functions (ht description)
               `(progn ,@(loop
                          for (func-name format-string params) in description
                          collect `(setf (gethash ',func-name ,ht)
                                         ,(make-canned-format-function-code format-string params)))))
DEF-CANNED-FORMAT-FUNCTIONS

CL-USER 79 > (pprint
              (macroexpand-1
               '(def-canned-format-functions
                 *foo*
                 ((zoom-zoom "~A-powered ~A"        (fuel device))
                  (delicious "~A ice cream"         (flavor))
                  (thematic  "~A: A ~A and ~A film" (title star co-star))))))

(PROGN
  (SETF (GETHASH 'ZOOM-ZOOM *FOO*)
        (LAMBDA (FUEL DEVICE)
          (FORMAT NIL "~A-powered ~A" FUEL DEVICE)))
  (SETF (GETHASH 'DELICIOUS *FOO*)
        (LAMBDA (FLAVOR)
          (FORMAT NIL "~A ice cream" FLAVOR)))
  (SETF (GETHASH 'THEMATIC *FOO*)
        (LAMBDA (TITLE STAR CO-STAR)
          (FORMAT NIL "~A: A ~A and ~A film" TITLE STAR CO-STAR))))

В вашем коде вынаписал бы на верхнем уровне:

(def-canned-format-functions
   *foo*
   ((zoom-zoom "~A-powered ~A"        (fuel device))
    (delicious "~A ice cream"         (flavor))
    (thematic  "~A: A ~A and ~A film" (title star co-star))))
0 голосов
/ 26 сентября 2018

Вы абсолютно можете делать то, что вы хотите в качестве функции.Это не будет самый красивый код, но он будет работать.Главное, что вы убрали из макросов, это правильно: они оцениваются во время компиляции [1] Я посмотрел на другой вопрос, на который вы ссылались, и пока они давали вам несколько хороших советов по оптимизации / рефакторингу, иногда вы просто хотите получить то, что вам нужно.хочу.Поэтому я попытался минимально изменить ваш код, чтобы он работал с одним изменением: вместо того, чтобы использовать макрос, который eval'd во время компиляции, я сделал его функцией, которая генерирует код, который затем eval'd ввремя выполнения:

(defun make-canned-format (template field-names)
    (eval `(lambda ,field-names
        (apply #'format `(nil ,,template ,,@field-names)))))

Теперь вы должны иметь возможность делать все, что имеет смысл для массового определения ваших функций (т. е. переносить макросы, циклы и т. д.). Следует помнить, что этот подходвыполнение повторных вызовов с одинаковыми именами шаблонов / полей будет неутешительным (поскольку он слепо восстанавливает один и тот же исходный код и оценивает его при каждом вызове во время выполнения, по сравнению с макросом, определение которого будет только один раз вычисляться во время компиляции.) Но так как выкажется, что он вызывается один раз для пары параметров и сохраняет результат генерации, это не будет проблемой.

[1], если вы не используете подход, использованный здесь для генерации макрос и оценить его во время выполнения.Это может сбивать с толку и даже сложнее отлаживать, чем уже могут быть макросы, но это можно сделать.

...