Thanks Michael for you quick response, that was definitely the pointer I needed!
After a bit of googling which didn't turn up much I ended up with: (defmacro form [state & elements] (let [m-state (gensym)] `(let [~m-state ~state] [:div.form.horizontal ~@(map (fn [[f m & rest]] `[~f (assoc ~m :value (get @(:values ~m-state) (:id ~m)) :on-change #(swap! (:values ~m-state) assoc (:id ~m) "UPDATED")) ~@rest]) elements)]))) (clojure.pprint/pprint (macroexpand '(form {:values test-atom} [:div {:id 1} "Hi there"]))) results in (let* [G__79805 {:values test-atom}] [:div.form.horizontal [:div (clojure.core/assoc {:id 1} :value (clojure.core/get @(:values G__79805) (:id {:id 1})) :on-change (fn* [] (clojure.core/swap! (:values G__79805) clojure.core/assoc (:id {:id 1}) "UPDATED"))) "Hi there"]]) My understanding is: 1(defmacro form [state & elements] 2 (let [m-state (gensym)] 3 `(let [~m-state ~state] 4 [:div.form.horizontal 5 ~@(map (fn [[f m & rest]] 6 `[~f (assoc ~m 7 :value (get @(:values ~m-state) (:id ~m)) 8 :on-change #(swap! (:values ~m-state) assoc (:id ~m) "UPDATED")) 9 ~@rest]) 10 elements)]))) 0 - ` means "emit, don't evaluate", ~@ means "splice, e.g. remove the outer sequence so [a ~@[1 2]] becomes [a 1 2] and ' means 'the symbol of rather than the value of'. 2 - declare m-state, which is lexically scoped to the macro and is bound to a random identifier created by gensym 3 - the back-tick (syntax-quote) returns the form rather than evaluating the form, so the macro will return (let* [m-8_324230_ ....]) The [~m-state ~state] is just bewildering though. 3 - in addition, the 'state' argument appears to be destructured, but only to one level so if the state contains an atom it is the var of the atom 4 - literal text emitted in-line 5 - splice the results of the map (i.e. rather than [:div.form.horizontal [child1 child2]] return [:div.form.horizontal child1 child2]) 5 - also destructure each element assuming [f m(ap) and 0 or more other args] 6 - emit [<f> where <f> is the first symbol, 'f' l in each element. Also prevent this being evaluated in the macro with the syntax-quote as (5) has introduced some new scope because of the ~@ - not sure. 6 - also associate onto the symbol m (which is assumed to be associative, e.g. a map)... 7/8 - extract data out of the 'run-time' (e.g. not macro-time) value of the provided state (magically captured under ~m-state) 9 - splice in the rest of the arguments, if any, that were part of the element 10 - and do that magic for each element This was all arrived at using a process only one step up from the good old "I don't _quite_ know what I'm doing so let's try random characters" so any improvements are most welcome! It does work but I am sure it is making peoples eyes bleed. Thanks again, and any and all thoughts are welcome! On Wednesday, 30 September 2015 21:41:31 UTC+1, Michael Blume wrote: > > #foo gensyms don't survive across a 'break' in quoting > > you can do > > `(let [foo# bar] > (other stuff > (foo# ...))) > > but you can not do > > `(let [foo# bar] > ~(for [i s] > `(foo# ...))) > > or anything like that. The workaround is to create a gensym explicitly > using (gensym), let that, and splice it in wherever you need the gensym. > > On Wed, Sep 30, 2015 at 1:29 PM Colin Yates <colin...@gmail.com > <javascript:>> wrote: > >> Hi all, >> >> I am banging my head against the wall - I think it is obvious but I have >> started too long: >> >> The use-case is that I want a form which takes a set of children. The >> form also takes in some form-wide state, like the form-wide validation, the >> values for each item etc. I want the macro, for each child, to decorate >> that child by extracting the validation errors and value from the form-wide >> state. >> >> So, assuming: >> - validation looks like {:name "Duplicate name" :age "You must be at >> least 0"} >> - form-values looks like {:name "a-duplicate-user" :age -1} >> >> then my form might look like: >> >> (form {:editing? true :values form-values :validation validation-report >> :on-change handle-form-change} >> [form/text {:id :name}] >> [form/number {:id :age}]) >> >> After the macro I want the following code: >> >> [:div.form.horizontal >> {:class "editing"} >> [form/text {:id :name :value "a-duplicate-user" :errors "Duplicate >> name" :on-click (fn [e] (handle-form-change :name (-> e .target .value])] >> [form/number {:id :age :value "-1" :errors "You must be at least 0" >> :on-click (fn [e] (handle-form-change :age (-> e .target .value))]] >> >> However, ideally the macro would _not_ emit the contents of the input as >> literals but would emit code that inspects the provided parameters at >> run-time (i.e. rather than :value "a-duplicate-user" I would much prefer >> :value (-> state values :name) as that will allow me to pass in an atom for >> example. >> >> I have tried so many variations and evaluating the state (e.g. (:editing? >> state)) works fine as the emitted code has the destructured values, but >> that doesn't work for an atom. >> >> Here is my attempt at trying to emit code that interrogates the provided >> parameter. >> >> (defmacro form [state & elements] >> (let [state# state] >> `[:div.form.horizontal >> {:class (if (:editing? state#) "editing" "editable")} >> ~@(map (fn [[_ {:keys [id]} :as child]] >> (update child 1 assoc >> :editing? (:editing? state#) >> :value `(-> (:values state#) 'deref (get ~id)) >> :on-change `(fn [e#] >> (js/console.log "E: " >> (cljs.core/clj->js e#)) >> ((:on-change state#) ~id (-> e# >> .-target .-value))))) >> elements)])) >> >> The error I am getting is that there is such var as the gen-sym's state# >> in the namespace. >> >> The generic thing I am trying to do is remove the boilerplate from each >> of the items in the form. >> >> Any and all suggestions are welcome. >> >> Thanks! >> >> -- >> You received this message because you are subscribed to the Google >> Groups "Clojure" group. >> To post to this group, send email to clo...@googlegroups.com >> <javascript:> >> Note that posts from new members are moderated - please be patient with >> your first post. >> To unsubscribe from this group, send email to >> clojure+u...@googlegroups.com <javascript:> >> For more options, visit this group at >> http://groups.google.com/group/clojure?hl=en >> --- >> You received this message because you are subscribed to the Google Groups >> "Clojure" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to clojure+u...@googlegroups.com <javascript:>. >> For more options, visit https://groups.google.com/d/optout. >> > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.