Tuesday, August 14, 2012

Another clojure macro tutorial (that no one should follow)

Disclaimer: This post shows you something you can do with macros. Not something you should do.

I like python.

You define functions

def something(x, y):
    return x + y

And you can document functions
def something(x, y):
"""Adds two things together"""
    return x + y

I also like clojure.

You can define functions
(defn something [x y]
  (+ x y))

And you can document functions
(defn something 
  "Adds to things together"
  [x y]
  (+ x y))

Documentation before the arguments? Despicable! If only there was a way of putting them in the right order.

Well, for the sake of argument let's try

Remember that in clojure code is data. A function is just a list, and we want to be able to define functions with some of the items of the list in a different order. At the moment a function definition list looks like this:

(function-name doc-string args function-body)

and we want to be able to make a function using the argument order

(function-name args doc-string function-body)

The first rule of matco club is "Don't write macros". So lets try:

First, how do we want our function (let's call it defndoc) to work? We want it to behave just like a normal function definition but with the docstring after the args.

(defndoc something [x y]
  "Adds to things together"
  (+ x y))

Now let's try to write it. We want to call our defndoc function and have that call defn with the arguments in the correct order.

(defn defndoc [fname args docs & body]
  (defn fname docs args body))

But this isn't going to work as our arguments are going to get evaluated. But this isn't what we want, looks like we will have to write a macro. This is how it looks

(defmacro defndoc [fname args docs & body]
    `(defn ~fname ~docs [~@args] ~@body))

Let's discuss the differences between this and our non-macro attempt.

First we use a syntax-quote (`). This is going to allow us to choose which bits of our list our evaluated and which are not. For example, the defn we want to be evaluated but the other parts we don't.

The next symbol is unquote (~) which tells clojure to evaluate these symbols.

The last symbol is unquote-split (~@) which tells clojure that there is a list of things here that needs to be expanded in place.

now if you do call macroexpand-1 using our defndoc macro on our function with the docstring following the arguments you will get the following

(clojure.core/defn something "Adds two things together" [x y] (+ x y))

Perfect, now we can sprinkle defndoc all over our code and have the docstring in the place we want it, but also keep clojure happy.

Now don't let me ever catch you doing this!

4 comments:

  1. It's generally idiomatic Clojure to put the arguments vector on its own line instead of on the same line as the function name as Python does, so that with a doc string you have

    (defn something
    "Adds to things together"
    [x y]
    (+ x y))

    and without it you have

    (defn something
    [x y]
    (+ x y))

    ReplyDelete
  2. More importantly, for variable-arity functions, the only place you can reasonably put the docstring is before any of the arglists.

    ReplyDelete
  3. This is a great way to look at how one might use macros. I haven't found a reason to yet.

    ReplyDelete
  4. I know this is an old post, but just have to leave a comment.

    "The first rule of macro club is "Don't write macros"" It seems a lot of people (not the author of this article in particular but a lot of other people) don't have a clue what macros are, what they are used for, what their function is and hence come up with silly rules such as never use macros or always use functions before using macros.

    So heres my two cents:

    A function is used to perform a certain procedure (yes I know in functional languages we don't call them procedures any more but just bear with me) at run time, i.e. to execute a certain piece of code at run time with set of parameters provided at run time. A function performs the programs functionality i.e. adds numbers, displays windows on the screen, launches nuclear warheads etc.

    A macro is executed at Compile time, not run time, that is when the program is compiled and any forms containing the macro are encountered, the macro function is called and the forms are literally replaced with the result returned by the macro function. That is a macro is used to perform a code transformation not program functionality, a macro is used to manipulate code not actually perform any function of the code.

    So that leaves us with the following rule: if you need to perform program functionality, i.e. execute a piece of code at run time to produce a result from a set of inputs, use a function. If you need to transform a piece of code at compile time such as to make it understood by the core language, use a macro. That in my opinion is a much better rule than "never use macros", I think the latter is said by people coming from C/C++ #define macros which indeed should never be used, however Lisp macros are a whole different breed and there is no reason why they shouldn't be used, (otherwise you might as well program in Python or Ruby).

    ReplyDelete