最初的动机是以下问题: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])
我们如何自动生成这些功能?
答案 0 :(得分:2)
虽然您无法将map
与宏一起使用,但您可以编写第二个宏来执行此功能。反过来,这可能需要编写第三个宏等,这是短语的起源" Macros All Way Down"如Clojure for the Brave and True和其他地方所述。
使用Clojure的intern
函数进行类似的问题was answered here。我们的问题与该问题略有不同,因为我们在这里以两种不同的方式使用intern
:
def
或defn
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-foo
,on-bar
& on-baz
反过来调用全球do-foo
,do-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