LOOP Macro Love

November 5th, 2007

I love the LOOP Macro. I didn’t at first, but the more code I read, the more value I see in it. I tried writing lots of do, dolist, and dotimes loops. But the expressive power of the LOOP Macro beckons me with its seductive mystery. A mini-language for iteration. A whole linguistic microcosm embedded in the macrocosm of Common Lisp. There always seems to be a new keyword to master.

So it got me thinking: why not have a conditional macro? Every program has conditionals, and there are some fairly regular patterns in which they’re used. And fairly regular patterns means it’s ripe for abstraction.

Pattern Matching

I’ve always liked the conditional syntax in some other languages. In Haskell, you can embed pattern matching (really unifying) expressions right into your conditional, and the unified variables get bound. Very convenient, as it saves a few very common lines of code. How many times have you seen this:

(defun foo (ls)

(if (null ls)

nil

(if (bar (first ls))

(cons (baz (first ls)) (foo (rest ls)))

(foo (rest ls)))))

It’s not the best way to do this in Lisp, but it isn’t uncommon to see it — I’m just using it as an illustration of my point. Using cond makes it slightly more readable. But in Haskell, you can do pattern matching. The equivalent in Lisp would look like this (:pat means we’re matching a pattern):

(defun foo (ls)

(haskell-cond

((:pat nil ls) nil)

((and (:pat (?first . ?rest) ls) (bar ?first))

(cons (baz ?first) (foo ?rest)))

(t (foo (rest ls)))))

Notice that the variables ?first and ?rest get bound to (first ls) and (rest ls) respectively inside both the test expression and the body of the winning test.

It’s even cooler when you match more interesting patters. Take, for instance, this pattern:

(:pat (?first ?first . ?rest) ls)

This will match true in the case where the list starts with two identical elements, such as when x is bound to '(1 1 2).

(:pat (?first a b) ls)

This matches lists of three elements, where the second and third are the symbols a and b, respectively.

Regular Expressions

A different kind of pattern is patterns in strings. For those, the standard is regular expressions. Check them out if you don’t know them yet.

Ruby has a conditional that binds (find the case statement in the solution written by Gordon) the groupings in regular expressions. Perl does something similar. So, this is what it would look like in Lisp (:re means match a regular expression):

(defun crazy-match (str)

(ruby-cond

;; Print the line number + offset if the line starts with that

((:re "^(\\d+)" str ((#'parse-integer line-num))) (print (+ line-num offset)))

;; Otherwise, extract out the last four characters and return them

((:re "(.{4})$" str (chars)) chars)))

This is a crazy function, but it shows what this matcher can do. In this example, I bound line-num to the first digits of the string, if it starts with digits. I also convert those to an integer, with the #'parse-integer option. The second expression has me bind four characters to the symbol chars.

What I really want to see

So these are two kinds of patterns I want to automatically bind inside of my cond expression. Here are my requirements for a suitable macro:

  • Allow normal cond statement syntax inside of itself — you don’t have to use the patterns, you can use regular Lisp functions
  • Patterns should be nestable inside predicates such as and / or
  • Any appropriate variable bindings should happen in the test expression and in the body of the matching expression
  • Match regular expressions and unifying expressions
  • Present an interface for adding new patterns

So, I did it

After many late-night coding sessions, I have finished a version 0.1 . My motivation, really, was to come up with something that had the alluring power of the LOOP Macro but applied to conditional pattern matching. I think I have succeeded. I can now do what I have described above. You can mix the patterns up, even inside the same test. For instance (please forgive the silly example):

(pcond

((and (:pat ((?fn ?ln) . ?rest ) '(("George" "Washington") ("John" "Adams")))

(:re "(.+)o" ?fn (before-o)))

(print before-o))

Here’s the code: pcond.lisp . It requires cl-ppcre, lisp-unit and cl-unification

pcond is the name of the macro, which expands to nested ifs. You can add new patterns with add-conditional. I’ll add better documentation as the code matures.

And I’ve re-written my time-window code. It’s cleaner, it compiles the matchers to nested lambdas (so it’s faster), and now it uses pcond. timewindow3.lisp

Let me know what you think of my pcond macro. I think it needs some work, still, but it is usable now. What other patterns would you like to see? I’d like to make the patterns easier to add. But we’ll see.

Popularity: 3% [?]

Server outage

October 30th, 2007

Sorry everybody.

My server went down today at about 11:00.  It’s back up now, with no loss of data.

Thanks for you patience!

Eric

Popularity: 2% [?]

New theme

October 24th, 2007

We have a new theme to go along with our new domain!

Let me know what you think.

Popularity: 2% [?]