Suppose I created a function:
(defn f [x y] (+ x y))
(f (+ 44 55) (+ 44 66))
I want to know if it runs correct. So I want to check its import values and output values. Then I write:
(verbosely! f (+ 44 55) (+ 44 66))
This macro transforms my code by adding several
(defmacro verbosely! [f & args]
(let [exp (str (cons f args))]
`(let [~'result (~f ~@args)]
(println "Calling:" ~exp)
(println "...With:" ~@args)
(println "Returns:" ~'result)
Then as it runs, the arguments and the results will be printed:
Calling: (f (+ 44 55) (+ 44 66))
...With: 99 110
I think it would help in some degree. Read more on GitHub https://github.com/mvc-works/verbosely
Do you know any other tools that would help in such scenarios?
That’s a useful tool, and a good use case for a macro.
A couple of tools that come to mind are:
This code, which inserts a break point into some code.
- If you’re using cider in Spacemacs or emacs, it also has a debugger.
- Sayid is another debugger for Clojure. I’d link it but “new users can only include two links in a post”.
Note that your macro has a couple subtle flaws:
args get evaluated 3 times. Try changing an argument to
(do (prn 'here) (+ 44 55)).
here will get printed three times. Of course, depending on the side effect, this could be quite surprising to the caller.
result could conflict with a binding at the caller. For example, try calling your macro like so:
(let [result (+ 44 55)] (verbosely! f result (+ 44 66))). Your macro will hijack the
result binding out from under the caller.
To solve the latter problem, say
result# instead of
~'result. This is called an “auto gensym”. A gensym is a generated symbol (see the
gensym core function) that is guaranteed not to conflict with other bindings. An auto gensym is available within the scope of a syntax quote (starting with backtick), and all references at the same level of syntax quoting will resolve to the same symbol.
To solve the former problem, use another gensym for the arguments. The easy rule here is, if you need to use an argument to a macro in more than one place in the exception, DRY it out into an auto-gensym’d binding in a let block.
I wrote a blog post with these and other tips for writing macros in Clojure, but the blog is currently down. If you’re interested I could share that with you somehow.
Cool. It takes time to digest. At least I changed
result# to fix the
gensym issue first.
I think Sayid does something similar.