Every now and then I try to build my macro muscle and every now and then need a spotter :). In my case i’m looking to build a macro that when given a seq of args (say via a -main function) create a let form that uses the associative property of vectors to bind to named values for easier use within the body form. So for example here is expected input and output
;; input
(with-args args
[hostname "127.0.0.1"
port "3030"]
[hostname port])
;; output
(let [{hostname 0
port 1
:or {hostname "127.0.0.1"
port "3030"}} (vec args)]
[hostname port])
Here you can see that you provide the actual args as a seq, and the bindings with defaults, the output is the associative destructuring on indexes with a default using :or. This is the macro i’ve come up with.
(defmacro with-args
{:style/indent 1}
[argv arg-bindings & body]
(let [sym-arg-bindings (map #(if (symbol? %1) (gensym %1) %1)
arg-bindings)
bind-list (->> (partition 2 sym-arg-bindings)
(map-indexed (fn [i v]
[(first v) i]))
flatten)]
`(let [(hash-map ~@bind-list
:or (hash-map ~sym-arg-bindings))
(vec ~argv)]
~@body)))
(println
(macroexpand '(with-args ["127.0.0.1 3030"]
[hostname "127.0.0.1"
port "6060"]
(println hostname)
(println port))))
Running the macroexpand gives the following issue stack trace. I think I offended the compile from the looks of it, but can’t exactly wrap my head around the issue. From what I guess it is expecting the [symbol value] form of the let binding instead of a map.
Call to clojure.core/let did not conform to spec.
#:clojure.spec.alpha{:problems ({:path [:bindings :form :local-symbol],
:pred clojure.core/simple-symbol?,
:val (clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))),
:via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/binding :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/local-name],
:in [0 0]} {:path [:bindings :form :seq-destructure],
:pred clojure.core/vector?,
:val (clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))),
:via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/binding :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/seq-binding-form],
:in [0 0]} {:path [:bindings :form :map-destructure],
:pred clojure.core/map?,
:val (clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))),
:via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/binding :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/map-binding-form :clojure.core.specs.alpha/map-bindings],
:in [0 0]} {:path [:bindings :form :map-destructure],
:pred clojure.core/map?,
:val (clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))),
:via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/binding :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/binding-form :clojure.core.specs.alpha/map-binding-form :clojure.core.specs.alpha/map-special-binding],
:in [0 0]}),
:spec #object[clojure.spec.alpha$regex_spec_impl$reify__2509 0x22ffdc30 "clojure.spec.alpha$regex_spec_impl$reify__2509@22ffdc30"],
:value ([(clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))) (clojure.core/vec ["127.0.0.1 3030"])] (println hostname) (println port)),
:args ([(clojure.core/hash-map hostname17622 0 port17623 1 :or (clojure.core/hash-map (hostname17622 "127.0.0.1" port17623 "6060"))) (clojure.core/vec ["127.0.0.1 3030"])] (println hostname) (println port))}