macrosclojurescriptdefn

Macro with a function definition including set! not working in Clojurescript


I'm trying to define a macro that should do the following:

I have the following defined in a clojure file:

(defmacro defprop
  [prop-name init-value]
  `(do
    (def ~prop-name ~init-value)
    (def ~(symbol (str prop-name "-other")) ~init-value)
    (defn ~(symbol (str prop-name "-11")) [] (set! ~prop-name 11))
    (defn ~(symbol (str prop-name "-set")) [new-val] (set! ~prop-name new-val))))

When invoked from clojurescript like this (cmacros is the namespace alias):

(cmacros/defprop production 350)

I get the definition of 'production' and 'production-other' right, the function 'production-11' works (and sets to 11 the value of production), but the last one does not work. I get different errors in different browsers. Chrome says "Uncaught SyntaxError: Unexpected token .". Firefox "SyntaxError: missing ) after formal parameters".

The offending javascript code pointed out by Firefox looks like this:

cgamemini.core.production_set = (function
  cgamemini$core$production_set(cgamemini.macros.new_val) {
    return cgamemini.core.production = cgamemini.macros.new_val;
  }
);

Which seems to have all the parenthesis right, although I'm no js specialist. What's going wrong? Why is the -11 definition working but not the -set?


Solution

  • The problem is new-val. Clojure macro expansion doesn't understand syntax so it doesn't know that new-val is supposed to be an argument -- any bare symbols that occur in a macro expansion at automatically namespaced to the current namespace. That's why you see cgamemini$core$production_set(cgamemini.macros.new_val) in your output. What you want to do is use hygienic macro syntax to indicate that a fresh symbol should be created that is not namespaced, e.g.

    `(defn ~(symbol (str prop-name "-set")) [new-val#] (set! ~prop-name new-val#))
    

    An alternative is to inject your symbol:

    `(defn ~(symbol (str prop-name "-set")) [~'new-val] (set! ~prop-name ~'new-val))