5
votes

I have a bunch of functions that map to and from some codes defined by an external system:

(defn translate-from-ib-size-tick-field-code [val]
  (condp = val
    0 :bid-size
    3 :ask-size
    5 :last-size
    8 :volume))

(defn translate-to-ib-size-tick-field-code [val]
  (condp = val
    :bid-size 0
    :ask-size 3
    :last-size 5
    :volume 8))

I'd like to make a macro to remove the duplication:

#_ (translation-table size-tick-field-code
                      {:bid-size 0
                       :ask-size 3
                       :last-size 5
                       :volume 8})    

I started the macro like this:

(defmacro translation-table [name & vals]
  `(defn ~(symbol (str "translate-to-ib-" name)) [val#]
     (get ~@vals val#)))

The resulting function body seems right, but the function name is wrong:

re-actor.conversions> (macroexpand `(translation-table monkey {:a 1 :b 2}))
(def translate-to-ib-re-actor.conversions/monkey 
     (.withMeta (clojure.core/fn translate-to-ib-re-actor.conversions/monkey      
     ([val__10589__auto__] 
        (clojure.core/get {:a 1, :b 2} val__10589__auto__))) (.meta ...

I'd like the "translate-to-ib-" to appear as part of the function name, instead of a prefix to the namespace, as it turned out.

How can I do this with clojure macros? If I am just doing it wrong and shouldn't use macros for this for some reason, please do let me know, but I would also like to know how to create function names like this to just improve my understanding of clojure and macros in general. Thanks!

2

2 Answers

7
votes

The macro issue is twofold:

1) You're using a backtick when quoting the form passed to macroexpand, which namespace-qualifies the symbols within:

`(translation-table monkey {:a 1 :b 2})
=> (foo.bar/translation-table foo.bar/monkey {:a 1, :b 2})

where foo.bar is whatever namespace you're in.

2) You're constructing the name of the defn item using the symbol name, which, when it is namespace-qualified, will stringify to "foo.bar/monkey". Here's a version that will work:

(defmacro translation-table [tname & vals]
  `(defn ~(symbol (str "translate-to-ib-" (name tname))) [val#]
     (get ~@vals val#)))

Notice that we're getting the name of tname without the namespace part, using the name function.

As for whether a macro is the right solution here, probably not :-) For a simple case like this, I might just use maps:

(def translate-from-ib-size-tick-field-code 
  {0 :bid-size
   3 :ask-size
   5 :last-size
   8 :volume})

;; swap keys & vals
(def translate-to-ib-size-tick-field-code
  (zipmap (vals translate-from-ib-size-tick-field-code)
          (keys translate-from-ib-size-tick-field-code)))

(translate-from-ib-size-tick-field-code 0)
=> :bid-size

(translate-to-ib-size-tick-field-code :bid-size)
=> 0

If speed is of the essence, check out case.

3
votes

Some unsolicited advice on a different point: (get ~@vals val#) is extremely suspicious. Your macro alleges to take any number of arguments, but if it gets more than two it will just do something that doesn't make any sense. Eg,

(translation-table metric {:feet :meters} 
                          {:miles :kilometers}
                          {:pounds :kilograms})

aside from being a terrible translation table, expands to code that always throws an exception:

(defn translate-to-ib-metric [val]
  (get {:feet :meters} 
       {:miles :kilometers}
       {:pounds :kilograms}
       val)))

get doesn't take that many arguments, of course, and it's not really what you meant anyway. The simplest fix would be to only permit two arguments:

(defmacro translation-table [name vals] 
  (... (get ~vals val#)))

But note that this means the value map gets reconstructed every time the function is called - problematic if it's expensive to compute, or has side effects. So if I were writing this as a macro (though see Justin's answer - why would you?), I would do it as:

(defmacro translation-table [name vals]
  `(let [vals# ~vals]
     (defn ~(symbol (str "translate-to-ib-" name)) [val#]
       (get vals# val#))))