代码之家  ›  专栏  ›  技术社区  ›  Claire Nielsen Dhiraj Sharma

在循环中生成任意参数化函数

  •  2
  • Claire Nielsen Dhiraj Sharma  · 技术社区  · 6 年前

    我正在尝试创建一组cookie-cutter函数,并将它们粘贴到哈希表中。到目前为止,我有一个宏可以扩展成这样的函数:

    (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 模式是非常典型的样板文件-Y,所以我尝试将其转换为循环:

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

    不幸的是,这起爆炸是因为 生成屏蔽格式宏 正在操作 价值 PARAMS 而不是价值 params ,因为它在编译时得到宏扩展,而不是在运行时进行计算。但当我问起 this question , 生成屏蔽格式宏 不会作为函数工作,因为它需要构造 lambda 形式在 编译时间 . (至少,我想这就是我从中学到的? 请告诉我这一点我错了!我希望我的函数工厂是一个函数,而不是宏! )

    我现在的想法是写一个 turn-this-list-of-templates-into-make-canned-format-macro-forms 宏而不是循环。这是正确的做法(或者至少是非精神错乱的做法),还是有更好的办法?

    2 回复  |  直到 6 年前
        1
  •  4
  •   Rainer Joswig mmmmmm    6 年前

    因为您在编译/宏扩展时知道参数,所以不需要应用:

    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))))
    
        2
  •  1
  •   blihp    6 年前

    作为一个函数,你绝对可以做你想要做的事情。这不是最漂亮的代码,但它会起作用。从宏中去掉的关键点是正确的:它们是在编译时进行评估的[1]我查看了您引用的另一个问题,当它们向您提供一些很好的优化/重构建议时,有时您只需要您想要的东西。因此,我试图对您的代码进行最小程度的更改,以使其能够在一次更改中正常工作:而不是使用在编译时进行eval的宏,而是将其作为一个函数,生成代码,然后在运行时进行eval:

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

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

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