Macros
A common point of confusion for lisp beginners is the macro. Few lispers can resist the opportunity to expound the beauty and elegance of the macro. However, as with many lisp concepts, most explanations are outside the range of the hobbyist’s experience.
In short, a macro is a type of function that allows the programmer to define new syntax for use in their programs. For example, I often hear Rubyists decry the lack of code blocks and Enumerable in other languages. So, let’s create a macro called “each” and bring this functionality to newLisp.
In Ruby, iteration is performed using the “each” method on an object. Let’s say we have the list, ["one", "two", "three"]. We want to print out “I can count to x” for each item in the list. We might use:
["one", "two", "three"].each do |x| puts "I can count to #{x}." end
While there are many ways to do this already in newLisp, such as:
(map (lambda (x) (println "I can count to " x)) ("one" "two" "three"))
…let’s see how to extend our syntax so we can write this as we would in Ruby. Macro syntax works almost exactly as it would in a regular function. The big difference, though, is that arguments passed to your macro are not evaluated. When you run (foo bar)
in lisp, foo is applied to the value of bar. With a macro, the symbol bar itself is passed, rather than its value, and the macro must evaluate the symbol to its value manually.
(define-macro (each object do iter) (set 'iter (trim (string iter) "|")) (dolist (obj (eval object)) (eval (set (sym (eval iter)) obj)) (doargs (a) (unless (= a 'end) (eval a)))))
Let’s see what’s going on here. Our macro, each, accepts three argument: object, do, and iter. Object refers to the object over which we wish to iterate. Do is a placeholder for the “do” in the each syntax. Iter is the pipe-encased variable name which we will iterate over.
(set 'iter (trim (string iter) "|"))
The first thing we do is trim off the pipe characters to get the variable symbol.
Next, we use a newLisp iterator, dolist, to iterate over object. Notice that here we must evaluate object so that we are working with the list itself.
Inside our dolist, we assign obj to the symbolic representation of iter, (sym iter). In our example above, this is the same as saying (set ‘x obj). Now we can access each member of the list, object, using the symbol ‘x.
Next, we use the doargs function to iterate over the remaining arguments (in newLisp, all arguments not specified in the function definition are available through the command args, and are iterable using doargs). The unless statement checks for and ‘end symbol. If the argument is not ‘end, it evaluates it, subbing in obj for x.
Careful readers will note that if there are commands after ‘end, they will be executed for a given value of x as well, but hey, it’s a blog entry, and we aren’t writing a Ruby interpreter :).
Now, the final product is:
(each '("one" "two" "three") do |x| (println "I can count to " x) end)
Which will print:
I can count to one
I can count to two
I can count to three