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:
first make some animals, I just use maps in this example, many people will use some other type of Object:
define the function we will be rebinding to be dynamic (which allows rebinding)
write a basic template macro to bind speak to the function in the animal:
and test it:
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 thespeak
symbol that explicitly introduces an un-qualified symbol into the scope: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.
If you really want to make animal types talk idiomatically, use Clojure Protocols:
Please note you can extend any type (class) with the speak method.
The syntax is a bit different for Java classes, please see the Protocols documentation for more information.