Jon Bristow
Jon Bristow

Reputation: 1725

Is there an idiomatic way to dry up similar function definitions in clojure?

I started with the following code (imagine more than this, but I think this gets the point across):

(defn fun1 [arg] {:fun1 arg})
(defn funA [arg] {:funA arg})
(defn funOne [arg] {:funOne arg})
(defn funBee [arg] {:funBee arg})

(defn -main [& args] (prn (fun1 "test-data")))

My next pass rendered it so:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))
(item-defn "fun1")
(item-defn "funA")
(item-defn "funOne")
(item-defn "funBee")
(defn -main [& args] (prn (fun1 "test-data")))

Is there a way to get this down to something like:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))
(map #(item-defn %) ["fun1" "funA" "funOne" "funBee"])
(defn -main [& args] (prn (fun1 "test-data")))

(I tried that in the repl, and it seems to work, but when I load a clj file with it in it, then it doesn't work. It gives me a "CompilerException" "Unable to resolve symbol: fun1")

Am I misusing macros? How would you do this?

Upvotes: 2

Views: 129

Answers (3)

zetafish
zetafish

Reputation: 2412

I wonder if you map expression really works in the REPL. I suspect that the fun1 and funA functions you have are still in your REPL because you first eval-ed (item-defn "fun1") and (item-defn "funA"). On my box I get:

(map #(item-defn %) ["fun1" "funA"])
;=> (#'user/p1__22185# #'user/p1__22185#)

So no function is defined with name fun1 or funA. The problem is that map is a function and item-defn is a macro. What happens in your map epxression is that item-defn gets macroexpanded at compile time at which moment the strings with function names are not visible. The macroexpander has no way of knowing that you want to use "fun1" as a name for your to be defn-ed function. Instead the macroexpander just sees % and then uses a gen-symed name as name of the defn-ed function. The map expression is evaluated at runtime but then it is too late for the macroexpanded function to do anything with the supplied strings.

The solution of Leonid works because he uses another macro to iterate over the function names. So that the iteration also happens at compile time. You see, macros are kind of contagious. Once you start, you cannot stop.

Upvotes: 1

Leonid Beschastny
Leonid Beschastny

Reputation: 51480

You may define another macro for this purpose, e.g.:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))

(defmacro items-defn [& names]
  `(do ~@(for [n names] `(item-defn ~n))))

then you'll be able to use it to define any number of functions:

(items-defn "fun1" "funA" "funOne" "funBee")

Upvotes: 5

Lee
Lee

Reputation: 144136

Inside your macro, the name is already a symbol so you can do:

(defmacro item-defn [name]
  `(defn ~name [arg#] {~(keyword name) arg#}))

then

(item-defn fun1)

Upvotes: 0

Related Questions