All right, let’s review what we’ve been doing for the last few days.
What Have We Done?
In a real sense, we’ve been writing a program that writes programs. A macro is a function that accepts Clojure code—represented as lists, vectors, symbols, strings, numbers, and other Clojure data types—and returns another list, vector, etc., that represents Clojure code. A macro can change its input any way it wants to, but of course the output needs to be valid Clojure code.
All computer languages allow us to make abstractions. We can abstract the data “given-name,” “surname,” “age,” and “address” into a class (or structure in Clojure) called “Person.” We can abstract the action of printing “Greeting” and a person’s name into the function “hello-world.” All languages in common use let us do that. Object-oriented languages also give us other abstractions for associating actions with data.
But lisps, including Clojure, go a step beyond that. They allow us to create abstractions of the syntax of the language. This allows us to control when and how expressions get executed.
Does this allow us to do things we cannot do in other languages? Strictly speaking, no.
But it allows us to do things more concisely and readably than we otherwise could. But because we can abstract more things away and worry less about them, we can build and understand programs that are more complex than we otherwise could be able to comprehend.
Domain Specific Languages
Another benefit of macros is that they allow us to create a mini-language with Clojure. If you look at Clojure’s source code, most of it is written in Clojure. And you have that same power. Once written, your code isn’t really that different than the code that makes up Clojure’s core.
You can use that ability to extend Clojure, to build up from its foundation, to create your own language ideally suited to the work you need to do.
You can do this in other languages, of course. It’s called creating domain specific languages. But in lisp, it is natural in a way it is in no other language.
The Spiderman Clause
Of course, as Peter Parker was told, “With great power comes great responsibility.” Like operator overloading or multiple inheritance, macros make it easy for you to shoot yourself in the foot. If you’re not careful, you can create something that is so far from Clojure that others have trouble understanding it, and you will have trouble getting your bearings when you come back to it in six months.
Remember: a little macros go a long way.
In the next posting, we’ll finally start on the steps involved in stemming.