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% [?]


9 Responses to “LOOP Macro Love”

  1. Time window matcher | LispCast on November 5, 2007 1:47 pm

    […] [edit: a new version is available, which I describe a bit here] […]

  2. vincent toups on November 5, 2007 2:55 pm

    So the color you have chosen for code in your blog here is really difficult to read - I am using Firefox on a Mac, if that might have anything to do with it. Its just really low contrast.

  3. kenny on November 8, 2007 6:52 pm

    Have you checked out John Foderaro’s legendary IF* macro? Available somewhere because it is in PAserve. Might even be a link to it on the Franz site IIRC.

  4. admin on November 9, 2007 11:11 am

    I have checked out IF*. I have thought long and hard about it, but in the end I decided that the benefits it purports to bestow (only one conditional to understand and look for, fewer parentheses, and not having to change from WHEN to IF to COND when you need to add more statements) are not sufficient reason to have a new construct. The macro, in my opinion, adds very little. Changing an IF to a WHEN when you no longer need the else clause is trivial.

    My PCOND macro does something new, which is binding variables based on pattern matching. It saves many levels of LET bindings that would be necessary to do the matching efficiently. It keeps everything neat and readable — it clearly shows what is bound where and why. If you’ve ever tried to write conditionals using pattern matching, you’d know that it is very messy. Check out the original time-window.lisp code from my Ruby Quiz post to see an example.

  5. Mark on November 9, 2007 11:52 am

    Have you read Paul Graham’s book “OnLisp”? I have not yet made it through it, but its reputation is that it is an eye-opening exposition on the use of Common Lisp macros.

    It is out of print, but is available for download online:

    http://www.paulgraham.com/onlisptext.html

    Thank you for your videos!

  6. Ivar Refsdal on December 10, 2007 11:41 am

    Hey & thanks for interesting code. Great stuff!

    You might want to paste the output from the pcond as well, for those who don’t have the libs to test it / are too lazy. I’m guessing it prints “Ge”.

    Also have you read the PAIP book ( http://www.amazon.com/Paradigms-Artificial-Intelligence-Programming-Studies/dp/1558601910/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1197303058&sr=8-1 )? I think you’d find it interesting if you haven’t.

    How about xml/http matching tags easily? And updating them.

  7. Announcement: PCOND | LispCast on December 10, 2007 9:07 pm

    […] just released a library called PCOND. I’ve posted about it before. You can read about it on […]

  8. Ivar Refsdal on December 11, 2007 5:55 am

    I see you recommend PAIP now, oh well. The widget didn’t work at first… Do you have any easier book to recommend? I feel it’s a bit over my head. I’m going to have a go at the little schemer &/ the seasoned schemer this Christmas I think, have you read that one? What do you think of it?

  9. admin on December 14, 2007 2:39 pm

    If PAIP is over your head, try SICP. It’s free online. They even have the whole MIT course on YouTube.

    For the book:
    http://mitpress.mit.edu/sicp/

    To download the videos:
    http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/

    or search YouTube/Google video for SICP

Trackback URI | Comments RSS

Leave a Reply

Name (required)

Email (required)

Website

Speak your mind