Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure - a let in a macro won't work

I have created a macro which creates a named dispatcher with 3 associates functions get-dispatcher, set-dispatcher and call-dispatcher to work with the dispatcher (they get a dispatching function, add one or call one). It all works just fine! However, now I want to automate the related functions names creation, thus I am putting all these internals of the macro into a let which defines that simple construction function. Note that in the code below only the get- function's name is constructed with that automation. The set- and call- ones name creation still has that manual smell.

(defmacro create-dispatcher [name]
  ;creates a set of dispatching functions tagged

  `(do
    ;define dispatcher
    (def ~(symbol name) ~(atom {}))

    (let
      [name-w-prefix (fn [x] (~(symbol (str x "-" name))))]
        ; -- define getter
        (defn (name-w-prefix "get")
          "get-dispatcher [tag]: get a dispatcher fn by tag"
          (~'[] (println "no tag is provided for '" ~(str name) "' dispatcher"))
          (~'[tag]
            (do
              (println "dispatcher '" ~(str name) "' called with '" ~'tag "' tag")
              ; return the tagged dispatcher
              ( (keyword ~'tag) @~(symbol name) )))

        )
        ; -- define caller
        (defn ~(symbol (str "call-" name))
          "get-dispatcher [tag & args]: call a dispatcher fn by tag and apply to the args"
          ~'[tag & args]
          (apply (~(symbol (str "get-" name)) ~'tag) ~'args)
          )
        ; -- define setter
        (defn ~(symbol (str "set-" name))
          ~'[tag fn]
          "add-dispatcher [tag fn]: add a dispatcher fn associated with the tag"
          (swap! ~(symbol name) assoc (keyword ~'tag) ~'fn)
          )
     )

    ; -- report
    (println "created dispatcher set for '" ~(str name) "' ok!")
    ))

However, there is a problem. The name-w-prefix in the let statement binding causes errors. How can I fix that?

(also any advices on improvement are welcome since I am a newb and that is almost the first thing that I wrote in Clojure)

like image 802
noncom Avatar asked Jan 15 '23 02:01

noncom


1 Answers

All symbols in a macro are resolved in the current namespace and expected to evaluate to a var. You could quote the name-w-prefix symbol, but that would risk colliding with symbols passed in to the macro during macro expansion. So, Clojure provides a special syntax for use in syntax-quoted forms for generating symbols - just append a # to the end of the symbol and Clojure will treat that as a quoted, auto-generated symbol. So in this case, replace occurrences of name-w-prefix with name-w-prefix# and you should be good to go.

Taking a step back and looking at what your overall goal is, I think you should move the name-w-prefix definition outside the syntax quotes and then use syntax-escape to call it. Otherwise, you'll get still more errors because defn requires a symbol, so once expanded the macro must produce a defn form that has a symbol as its second item. Something along the lines of:

(defmacro create-dispatcher [name]
  (let [name-w-prefix #(symbol (str % "-" name))]
    `(do
       (def ~(symbol name) (atom {}))
       (defn ~(name-w-prefix "get")
         ([] (println "no tag provided"))
         ([tag#] (println "called with tag" tag#))))))

Note that I've changed ~'[tag] to [tag#] in the defn body in accordance with what I was talking about above.

like image 67
Alex Avatar answered Jan 18 '23 21:01

Alex