如何在没有宏的情况下自动创建Clojure`defn`函数?

时间:2017-05-13 22:13:39

标签: clojure macros code-generation

最初的动机是以下问题:Mapped calls to clojurescript macro

假设您想要自动创建许多类似的功能(即无需手动编写所有功能)。假设我们有一些预先存在的函数,我们希望用处理程序包装它们以进行某种回调:

(defn do-foo [] (println "I foo'ed"))
(defn do-bar [] (println "I bar'ed"))
(defn do-baz [] (println "I baz'ed"))

(defn manual-on-foo [] (do-foo))
(defn manual-on-bar [] (do-bar))
(defn manual-on-baz [] (do-baz))

(println "Calling manual-on-* functions")
(manual-on-foo)
(manual-on-bar)
(manual-on-baz)

结果:

Calling manual-on-* functions
I foo'ed
I bar'ed
I baz'ed

我们希望自动而不是手动生成包装函数。

您可能认为需要一个宏来创建此功能,这是一个解决方案。但是,宏的缺点是它们不能作为参数传递给另一个函数,例如map。因此,我们可以写一个像:

的宏
(generate-fn :foo)  ;=> creates `on-foo` w/o hand-writing it

但以下情况会失败:

(map generate-fn [:foo :bar :baz])  

我们如何自动生成这些功能?

1 个答案:

答案 0 :(得分:2)

概述

虽然您无法将map与宏一起使用,但您可以编写第二个宏来执行此功能。反过来,这可能需要编写第三个宏等,这是短语的起源" Macros All Way Down"如Clojure for the Brave and True和其他地方所述。

使用Clojure的intern函数进行类似的问题was answered here。我们的问题与该问题略有不同,因为我们在这里以两种不同的方式使用intern

  • 创建与defdefn
  • 类似的全局变体
  • 使用var-get
  • 访问全局变量的值

功能解决方案

使用intern允许我们编写以下代码,以便在不使用宏的情况下自动生成on-*函数:

(defn generate-onstar-f
  [event-kw]
  (let [
    event-str (name event-kw)
    do-fn-sym (symbol (str "do-" event-str))
    on-fn-sym (symbol (str "on-" event-str))
    new-fn    (fn on-fn-sym []
                (let [the-var (intern 'tst.clj.core do-fn-sym) ; get the var the symbol 'do-fn-sym' points to
                      the-fn  (var-get the-var) ] ; get the fn the var is pointing to
                  (the-fn))) ]
    (intern 'tst.clj.core on-fn-sym new-fn) ; create a var 'on-fn-sym' pointing to 'new-fn'
    (println "Created" on-fn-sym)))

(println \newline "*** generating functions ***")
(mapv generate-onstar-f [:foo :bar :baz]) ; creates and interns a functions:  my-foo, my-bar, my-baz

(println \newline "Calling automatically generated on-* functions")
(on-foo)
(on-bar)
(on-baz)

结果:

 *** generating functions ***
Created on-foo
Created on-bar
Created on-baz

 Calling automatically generated on-* functions
I foo'ed
I bar'ed
I baz'ed

所以我们看到我们创建了函数on-fooon-bar& on-baz反过来调用全球do-foodo-bar和& do-baz功能。而且我们不需要使用宏!

在Clojure中,var有点像一个看不见的"中间人"在on-foo之类的符号和它指向的值之间(本例中的函数)。有关详细信息,请参阅相关帖子:

When to use a Var instead of a function?

宏解决方案

如前所述,人们可以使用宏来调用另一个宏,这样就可以解决宏不能用于map等高阶函数(HOF)的问题。在这里,我们定义了一个新的宏run-macro,以替换我们无法与map一起使用的generate-onstar-f HOF:

(defmacro generate-onstar-m
  [event-kw]
  (let [event-str (name event-kw)
        do-fn-sym (symbol (str "do-" event-str))
        on-fn-sym (symbol (str "on-" event-str "-m"))]
    (println "Creating" on-fn-sym)
    `(defn ~on-fn-sym []
       (~do-fn-sym))))

(println \newline "Using Macro")
(generate-onstar-m :foo)
(on-foo-m)

(defmacro run-macro
  "Run the specified macro once for each arg"
  [root-macro args]
  `(do
    ~@(forv [item args]
      `(~root-macro ~item))))

(println \newline "Generating on-*-m functions using `run-macro`")
(run-macro generate-onstar-m [:foo :bar :baz])
(on-foo-m)
(on-bar-m)
(on-baz-m)

结果:

 Using Macro
Creating on-foo-m
I foo'ed

 Generating on-*-m functions using `run-macro`
Creating on-foo-m
Creating on-bar-m
Creating on-baz-m
I foo'ed
I bar'ed
I baz'ed