【问题标题】:Generating arbitrarily-parameterized functions in a loop在循环中生成任意参数化的函数
【发布时间】:2018-09-25 22:58:44
【问题描述】:

我正在尝试创建一堆千篇一律的函数并将它们粘贴在哈希中。到目前为止,我已经有了一个可以扩展成这样一个函数的宏:

(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)))

那个重复的setfgethashmake-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 上运行的,因为它在编译时进行了宏扩展,不在运行时评估。但正如我在询问this question 时了解到的,make-canned-format-macro 不能作为函数工作,因为它需要在编译时构造lambda 表单。 (至少,我认为这是我从中学到的东西?请告诉我我在这一点上错了!我希望我的函数工厂是一个函数,而不是一个宏!

我目前的想法是编写一个turn-this-list-of-templates-into-make-canned-format-macro-forms 宏而不是循环。这是正确的做法(或至少不是疯狂的做法),还是有更好的方法?

【问题讨论】:

    标签: macros lisp common-lisp code-generation


    【解决方案1】:

    由于您在编译/宏扩展时知道参数,因此不需要应用:

    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))))
    

    【讨论】:

    • 您的“通过宏构建”部分正是我想要的!谢谢!在一个小的旁注中,您评论了多次引用 - 我这样做是为了让我自己清楚 zoom-zoom 没有被用作函数或任何东西;我明确地想要关键字本身。这是一件奇怪的事情吗?有没有更惯用的方式来表达我的意图?
    • @ClaireNielsen:如果你想要一个关键字,那么你可以使用:zoom-zoom
    • 呃,这是个脑残。我的意思是“符号”。对不起。 (我没有编辑我之前的评论,这样你的评论仍然有意义。)
    • @ClaireNielsen:我知道,但是当您考虑时,关键字符号将是合适的...
    【解决方案2】:

    你绝对可以做你想做的事情。它不会是最漂亮的代码,但它会工作。您从宏中获得的关键是正确的:它们是在编译时评估的[1]想。因此,我尝试对您的代码进行最低限度的更改以使其与一个更改一起工作:而不是使用在编译时进行评估的宏,而是将其设为生成代码的函数,然后在运行时间:

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

    现在你应该能够为你的函数的大规模定义做任何有意义的事情(即包装宏、循环等)关于这种方法要记住的是,使用相同模板重复调用的性能/字段名称将令人沮丧(因为它盲目地重新生成相同的源代码并在运行时每次调用都对其进行评估,而宏的定义只会在编译时评估一次。)但是由于您似乎每对调用一次参数和存储生成的结果,这不是问题。

    [1] 除非您使用此处使用的方法生成宏并在运行时对其进行评估。这可能会令人困惑,甚至比已经使用的宏更难调试,但它可以做到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-11
      • 1970-01-01
      • 2012-04-28
      相关资源
      最近更新 更多