Dynamic binding exists for a reason and it has lots of great uses, so no worries about being flamed for seeking to understand it :-) There is some confusion floating around many older Clojure tutorials that pre-date the need for adding ^:dynamic metadata to vars that you expect to dynamically rebind.
This first example uses dynamic binding by rebinding an existing name. This removes the need for the macro to introduce a new symbol:
first make some animals, I just use maps in this example, many people will use some other type of Object:
(def dog {:sound #(str "wooooof")})
(def cat {:sound #(str "mewwww")})
define the function we will be rebinding to be dynamic (which allows rebinding)
(defn :^dynamic speak [] (println "eh?"))
write a basic template macro to bind speak to the function in the animal:
(defmacro with-animal [animal & body]
`(binding [speak (:sound ~animal)]
~@body))
and test it:
(with-animal dog
(println (str "Dog: " (speak) "\n")))
Dog: wooooof
and now the "advanced version" which just introduces a symbol
speak
into the scope using a let with no need for dynamic binding. This is not to say that binding is bad in some way, it just more closely fits your desire to not write
(let-binding [speak (fn [] "meow")] ...)
This type of maco is called anaphoric (if you're into such fancy names):
the important part is the ~'
before the speak
symbol that explicitly introduces an un-qualified symbol into the scope:
user> (defmacro with-animal [animal & body]
`(let [~'speak (:sound ~animal)]
~@body))
#'user/with-animal
user> (with-animal dog
(println (str "Dog: " (speak) "\n")))
Dog: wooooof
nil
I hope that the contrast between these two examples serves to answer your question about binding behavior from an object into a scope. The first example binds the value for the body of the maco AND anything that is called from that body. The second example introduces the name ONLY for the body of the macro.