| > I would pick function over macros every single time. I should have went into a little more detail, maybe. The general advice has always been "prefer data over functions, prefer functions over macros", but I don't think "prefer" is strong enough. I would rephrase it as: "Prefer data over functions. Only use macros if there's absolutely no other option." That means that macros shouldn't be used to make code more terse, or more convenient, or more "pretty". They should be used when they make code possible that wouldn't otherwise be possible (at least without jumping through a lot of hoops). For all my complaints, core.async is actually a good example of a good use of macros, as far as a library goes. It adds functionality that would be quite difficult to do cleanly without macros. My complaint is just that a macro implementation of something so integral is very much inferior to an implementation that was part of the language itself. I don't think async should be something tacked on to a language as an afterthought. An example of what I consider a bad use of macros would be something like this: Imagine you have a system to register event handlers that can then be triggered by name: (defhandler my-event (println "my-event triggered"))
(trigger! my-event)
Many clojure libraries exist with patterns like this, especially from earlier on before the community began to shift more closely to the `data > functions > macros` mentality.A macro-less version might look something like this: (register-event! :my-event (fn [] (println "my-event triggered")))
; or maybe: (def my-event (register-event (fn [] ...)))
; or maybe even (def registry2 (register-event registry :my-event (fn [] ...)))
(trigger! :my-event)
Where the expression that the macro makes possible is wrapped in an anonymous function and the naming is explicit. Its not quite as convenient as the macro version, but it avoids magic and therefore surprises, and its more flexible because you can compose it or operate on it like any other function. |