A few month ago, I wrote a Clojure macro, looks like this:
It means if current env is test, then generate normal function, otherwise generate async function and log the error if catched. We need this because when we call some functions, we don’t want to wait them to return, but we still want to catch their exception and log it. But when in test env, we want them to throw exception to make error more obvious. We determine env by environment variable, and default to test env if not provided.
After I finished this macro, and kicked it online, everything seems fine. But somehow I recall this macro while I was doing something else, and found it has a bug that is difficult to detect. The bug stems from the way I treat it as normal function instead of macro.
Because macro expansion happens at compile time instead of runtime, this macro needs env variable at compile time, otherwise it will generate normal function. But normally we won’t set env variable at compile time (lein jar
), and only set env variable at runtime, because normally env variables only take effect at runtime. So we didn’t get async function online at all, they’re all normal functions. Solution for this is simple, just set env variable before compile.
This kind of problem is hard to find, because no matter what function we get, the behavior is identical, just a little bit slower.
It seems mastering macro is not that simple. But macro is less mysterious to me now, when I was reading , macro is something I can’t understand, because the book tells all the goodness about macro without telling any principle about it, this just makes it mysterious, but after you understand all the principle, it’s just a tool to generate code for you, the goodness is you can use normal function in macro just like in other function, but actually macro is not as flexible as function sometimes: they can’t being passed as value.
Now, I’m quite understand the rule for macro: “don’t write macro if you can”.