I realize the following is a bad idea for many reasons. I also realize that given I have a stackoverflow rep of 23, it's nature to assume that I'm a newb learning to program. However, please humor me, and focus on the "how can we do this" rather than "why do you want to do this / you don't want to do this" aspect.
What I want:
(def dog (Dog. ...))
(def cat (Cat. ...))
(with-animal dog
(println (str "Dog: " (speak) "\n")))
(with-animal cat
(println (str "Cat: " (speak) "\n")))
to output:
Dog: woof
Cat: meow
So basically, I want with-animal to be a macro s.t. all occurences of the "speak" function call gets mapped to the object I'm calling the block with.
In particular, I don't want to write:
(let-binding [speak (fn [] "woof")] ...)
(let-binding [speak (fn [] "meow")] ...)
Rather, I want the with-animal to make the speak function map to some method of the object I'm calling with.
Is there a clean way to do this in Clojure?
Thanks!
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:
(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
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