Tuesday, October 5, 2010

Lesson 8 - Macros! Macros! Everywhere

We saw anonymous functions in Lesson 4. Here is a simple anonymous function.
(fn [name] (println "Hello" name))
The problem with the above for me, coming after using javascript is that "fn" is too cryptic for my liking. I would have liked to have done
(function [name] (println "Hello" name))
I would rather write "function", which is more verbose, but makes code easier to understand for people coming to clojure from other languages. Now if I had some way, to kind of magically transform the code in the second line using "function" form to the first line using "fn", before the code is actually executed, I could have my way!

That is exactly what the defmacro function does. It takes a macro definition in the code, and replaces it with executable code at compile time. Macro's are like templates. It so happens that they generate executable code! This is what makes Macro's in Clojure and other dialects of Lisp so powerful, and so different to Macro's in other languages. Here is our simple trivial macro.

(defmacro function [args & body]
  `(fn ~args ~@body))

The defmacro function takes a name, "function" in this case and set of arguments. In this case the first argument is args, and the second "& body" represents a variable number of arguments. The backtick "`" indicates that "(fn ~args ~@body)" should not be evaluated. Instead what happens is that at compile time new code is generated to replace the macro with the "~" tilde arguments replaced with the actual arguments. So when the compiler encounters
(function [name] (println "Hello" name))
it replaces "~args" with "[name]" and "~@body" with whatever the rest of the arguments are, in this case "(println "Hello" name)". So the new code replacing the whole macro will be
(fn [name] (println "Hello" name))
which is what we wanted to begin with.

It is important to note that the arguments you passed to defmacro "[name] (println "Hello" name)" are not evaluated and are passed verbatim to the template. If you had passed the same arguments to a function on the other hand they would have been evaluated immediately.

Now let us actually execute some code.

(defmacro function [args & body]
  `(fn ~args ~@body))

((function [name]
  (println "Hello" name)) "World!")

What we are doing above is creating an anonymous function using the "function" macro and calling that function immediately with argument "World!". To execute an anonymous function immediately simply pass the function as the first argument of a list with the rest of the arguments in the list being the arguments to be passed to the function. The point to note above is that the variable "name" passed to the anonymous function is only visible within the function body .ie. it is a lexically scoped variable, just like the variables created with the "let" function we saw in lesson 7.

(defmacro function [args & body]
  `(fn ~args ~@body))

(println (macroexpand-1 '(function [name]
  (println "Hello" name))))

Run the program above. The macroexpand-1 function takes a quoted' macro and expands it to its executable form. In this case the expansion is
(clojure.core/fn [name] (println Hello name))

The defn function which we used for defining functions is also a macro. Here is a simple defn macro using our own "function" macro.

(defmacro function [args & body]
  `(fn ~args ~@body))
(defmacro define-function [fn-name args & body]
  `(def ~fn-name (function ~args ~@body)))

(define-function say-hello [name]
  (println "Hello" name))

(say-hello "World!")

Run the above code. So we created two macro's. "function" works exactly like "fn". And "define-function" works exactly like "defn".

Next we take on Multimethods and routing for our server.

No comments:

Post a Comment