Clojure: Macros vs. Functions
So I’ve got a bunch of functions that have this ugly try-catch logic in them here’s an example of one of those functions:
(defn get-something [id] (try (db/get-something id) (catch Exception e nil)))
Basically, I’m wrapping some database operation and introducing some error handling with a default-value. The try-catch is really ugly though and repetitive though so I’d like to somehow extract it and reduce the duplication. Simple enough, I’ll just write a private function that takes a function and a default-value as arguments. Problem solved…
(defn- with-error-handling [default-value fn] (try fn (catch Exception e default-value)))
I’ll run my specs to verify and… Hey! what gives I have a bunch of failures. That clearly didn’t work, but why?
Well the issue is in how I’m passing in the function. if we look at the example below with of the
get-something function using
with-error-handling function we see that I’m calling
db/get-something with the id params this evaluates that function
and passes the result to
with-error-handling. To little too late, the exception has already been raised by calling
with-error-handling does not do the job, for which it was created.
(defn get-something [id] (with-error-handling nil (db/get-something id))) ;; This is the same as (defn get-something [id] (with-error-handling nil some-exception))
In Clojure, functions transform values into other values. In our case, we actually want to transform our code into something else.
;; We want this (db/get-something id) ;; To turn into this (try (db/get-something id) (catch Exception e default-value))
Enter macros, which do just that. transform code into some other kind of code. This makes macros extremely powerful, In fact, if you felt so inclined you could write an entirely new programming language using Clojure macros. But first, back to the problem at hand.
I want to create a
with-error-handling macro. how do I go about it? To start, It probably helps to understand how macros transform code into different code
and for that we probably need to dive deeper into the bowels of Clojure. I’m saving that exploration for the following blog post. Until next time…