How to create Clojure `defn` functions automatically without macros?

左心房为你撑大大i 提交于 2019-12-02 09:19:02
Alan Thompson

Overview

While you can't use map with a macro, you could write a second macro to perform this function. This may, in turn, require writing a third macro, etc, which is the origin of the phrase "Macros All the Way Down" as described in Clojure for the Brave and True and other places.

A similar question was answered here by using Clojure's intern function. Our problem is a little different than that question, since here we use intern in two different ways:

  • To create a global var like with def or defn
  • To access the value of a global var using var-get

Function Solution

Using intern allows us to write the following code to automatically generate the on-* functions without using macros:

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

with results:

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

So we see that we created the functions on-foo, on-bar & on-baz which, in turn, call the global do-foo, do-bar, & do-baz functions. And we didn't need to use macros!

In Clojure, the var is somewhat of an invisible "middle-man" between a symbol like on-foo and the value it points to (a function in this example). For more information please see the relate post:

When to use a Var instead of a function?


Macro Solution

As mentioned previously, one could use a macro to invoke another macro, side-stepping the problem that macros can't be used with higher-order-functions (HOF) like map. Here we define a new macro run-macro, to replace the map HOF we can't use with generate-onstar-f:

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

with results:

 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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!