11

I am working on a Clojure project and I often find myself writing Clojure macros for DSLs, but I was watching a Clojure video of how a company uses Clojure in their real work and the speaker said that in practical use they do not use macros for their DSLs, they only use macros to add a little syntactic sugar. Does this mean I should write my DSL in using standard functions and then add a few macros at the end?

Update: After reading the many varied (and entertaining) responses to this question I have realized that the answer is not as clear cut as I first thought, for many reasons:

  1. There are many different types of API in an application (internal, external)

  2. There are many types of user of the API (business user who just wants to get something done fast, Clojure expert)

  3. Is there macro there to hide boiler plate code?

I will go away and think about the question more deeply, but thanks for your answers as they have given me lots to think about. Also I noticed that Paul Graham thinks the opposite of the Christophe video and thinks macros should be a large part of the codebase (25%):

http://www.paulgraham.com/avg.html

customcommander
  • 17,580
  • 5
  • 58
  • 84
yazz.com
  • 57,320
  • 66
  • 234
  • 385

4 Answers4

12

To some extent I believe this depends on the use / purpose of your DSL.

If you are writing a library-like DSL to be used in Clojure code and want it to be used in a functional way, then I would prefer functions over macros. Functions are "nice" for Clojure users because they can be composed dynamically into higher order functions etc. For example, you are writing a functional web framework like Ring.

If you are writing a imperative DSL that will be used pretty independently of other Clojure code and you have decided that you definitely don't need higher order functions, then the usage will be pretty similar and you can chose whichever makes most sense. For example, you might be creating some kind of business rules engine.

If you are writing a specialised DSL that needs to produce highly performant code, then you will probably want to use macros most of the time since they will be expanded at compile time for maximum efficiency. For example, you're writing some graphics code that needs to expand to exactly the right sequence of OpenGL calls......

mikera
  • 105,238
  • 25
  • 256
  • 415
7

Yes!

Write functions whenever possible. Never write a macro when a function will do. If you write to many macros you end up with somthing that is much harder to extend. Macros for example cant be applied or passed around.

Christophe Grand: (not= DSL macros)

http://clojure.blip.tv/file/4522250/

nickik
  • 5,809
  • 2
  • 29
  • 35
  • Do I get it right that you're advocating an interpretation over a compilation? – SK-logic Mar 28 '11 at 10:38
  • No, I think we are talking about diffrent kinds of DSLs. I talk about normal APIs made nice (a mini DSL for that library) not something like embedded Prolog. (And I think most people don't often write complet language implementation) – nickik Mar 28 '11 at 11:02
  • @nickik, it does not matter - even if your DSL is just a wrapper around, say, Swing, it is much better compiled and *statically verified* than interpreted (and failing in a runtime). – SK-logic Mar 28 '11 at 11:07
  • The consensus of the community is that nickik is right and you are so very wrong that it's visible from satellite. – Rayne Mar 28 '11 at 13:33
  • @SK-logic You would have to build your staticall verification into your macros and most people want do that anyway. You just want to wrap the boilerplate away. What your are advocation is Overengineering. You neglect that macros are harder to write then functions, witch makes your code harder to read. – nickik Mar 28 '11 at 13:55
  • 2
    @nickik, the main point that Christophe made is that "writing DSLs is *hard* (at least *for him*)". It is not nearly enough of an argument for choosing high order functions instead of AST transforms - it is just an indication that *he* is doing it entirely the wrong way. Actually, the vast majority of the Clojure community is doing it the wrong way, trying to implement complex, monolithic macros instead of chains of trivial transforms. – SK-logic Mar 28 '11 at 13:58
  • 1
    @nickik, macros are *easier* to write than the functions. Much, much easier. Check out my examples from the other comment. If it is hard for you, you're doing it wrong. Macros should be trivial. Why you, functional programmers, are so good in decomposing your problem into small and comprehendable functions, but unable to do the very same thing with the tree transformers? Just start thinking of the macros in the right terms: languages are translated one into another in a sequence of tree transforms, and each transform should do one simple thing. Then chain them together any way you like. – SK-logic Mar 28 '11 at 14:01
  • I have not made the discovery that macros are easier but then again I have not writen lots of DSLs. I will look at the exampels you provided. – nickik Mar 28 '11 at 18:06
3

No!

Don't be afraid of using macros extensively. Always write a macro when in doubt. Functions are inferior for implementing DSLs - they're taking the burden onto the runtime, whereas macros allows to do many heavyweight computations in a compilation time. Just think of a difference of implementing, say, an embedded Prolog as an interpreter function and as a macro which compiles Prolog into some form of a WAM.

And do not listen to those who say that "macros cant be applied or passed around", this argument is entirely a strawman. Those people are advocating interpreters over compilers, which is simply ridiculous.

A couple of tips on how to implement DSLs using macros:

  • Do it in stages. Define a long chain of languages from your DSL to the underlying Clojure. Keep each transform as simple as possible - this way you'd be able to easily maintain and debug your DSL compiler.
  • Prepare a toolbox of DSL components that you will reuse when implementing your DSLs. It should include target languages of different semantics (e.g., untyped eager functional - it is Clojure itself, untyped lazy functional, first order logic, typed imperative, Hindley-Millner typed eager functional, dataflow, etc.). With macros it is trivial to combine properties of all that target semantics seamlessly.
  • Maintain a set of compiler-building tools. It should include parser generators (useful even if your DSLs are entirely in S-expressions), term rewriting engines, pattern matching engines, implementations for some common algorithms on graphs (e.g., graph colouring), etc.
SK-logic
  • 9,605
  • 1
  • 23
  • 35
  • 1
    It doesn't particularly matter what my opinion is, in this case, because the general opinion of the entire Clojure community is that you're advocating the opposite of what people should strive for with their DSLs. Not trying to start an argument or attack you, but I have to say, I hope budding Clojurians who don't know better find out quickly that they need to ignore you. Also "little coward"? Calm down, sir, let's be civil. – Rayne Mar 28 '11 at 13:54
  • 4
    @Rayne, a so called "community" believes that OOP is the most superior approach to the software architecture. The "community" believe that your beloved functional programming thingy is just for crazy geeks and academics, and completely unacceptable in an enterprise. Do you agree with the majority, with your "*community*"? Then why are you referring to an unjustified, counter-productive view of that majority in this case? – SK-logic Mar 28 '11 at 14:08
  • 1
    Well, that wasn't the point I was making. The point that I was making is that using macros on a really-have-to basis is a convention of sorts. Like not using single-segment namespaces is a convention. The reason for this convention has been made clear. Especially in Christophe Grand's talk. I don't really have anything to add to it, and since you've all but discarded the ideas and conclusions before they ever entered your ears, I'm not sure I can convince you otherwise. Alas, I am sorry for my cowardice. :) – Rayne Mar 28 '11 at 14:15
  • 1
    @Rayne, that's exactly the point I'm opposing to. Christophe is building his argument on a completely wrong basis, because macros are not hard to write. Compilers are easier than interpreters, for one very obvious reason - you've got to implement an interpreter each time you're writing a DSL, and you can reuse the existing compiler and *all* the other DSLs you already wrote if you're using macros. A convincing argument would be an example of a function-based DSL that is easier, more readable and more maintainable than any possible macro-based one. I'm open for this sort of arguments. – SK-logic Mar 28 '11 at 14:20
  • 1
    @SK-logic I think that Grand's demo and SPJ's Contracts are examples of "function-based DSL that [are] easier, more readable and more maintainable than any possible macro-based one." Maybe not *any* macro-based one, but any that I've seen. – wilkes Mar 28 '11 at 16:17
  • 1
    @wilkes, that's not the easiest DSL to understand, but from what I've managed to get, a macro implementation will be easier - the whole `eval` part will be missing. I'll try to jot a simple implementation. – SK-logic Mar 28 '11 at 17:51
2

Here's an example of a DSL in Haskell that uses functions rather than macros:

http://contracts.scheming.org/

Here is a video of Simon Peyton Jones giving a talk about this implementation:

http://ulf.wiger.net/weblog/2008/02/29/simon-peyton-jones-composing-contracts-an-adventure-in-financial-engineering/

Leverage the characteristics of Clojure and FP before going down the path of implementing your own language. I think SK-logic's tips give you a good indication of what is needed to implement a full blown language. There are times when it's worth the effort, but those are rare.

wilkes
  • 324
  • 2
  • 3
  • Once you've got all that components, implementing even the most trivial DSL compiler is much easier than any high-order-functions-based interpreter. See the following for examples: http://www.meta-alternative.net/calc.pdf and http://www.meta-alternative.net/pfront.pdf – SK-logic Mar 28 '11 at 12:24