Chaining Function Calls

I like Lisp’s prefix syntax. It’s consistent, has natural structure, and makes code-manipulation macros possible. But it’s not always the easiest to read or write. For example, I often want to apply several successive transformations to the same chunk of text. In Perl, I could use the default variable $_ and then just write a bunch of regular expressions:

s/this/that/g;
s/old/new/g;
s/foo/bar/g;

Very succinct, but a tad cryptic. But the equivalent in Common Lisp, using the CL-PPCRE regular expression library, is much worse:

(regex-replace-all "foo" 
		   (regex-replace-all "old"
				      (regex-replace-all "this" string "that")
				      "new")
		   "bar")

CL-PPRCE’s regex-replace-all function puts the original string in between the regex and replacement string in its argument list, which makes the syntax awkward. I usually avoid writing nested expressions like the one above and instead factor each replacement out into a separate function:

(defun replace-foo (string)
  (regex-replace-all "foo" string "bar"))

(defun replace-old ...)

(defun replace-this ...)

(replace-foo (replace-old (replace-this string)))

But who wants to define three extra functions just for one expression?

Now I’m exploring Ruby, and was pleased to find how easy it is to write this:

string.gsub('this','that').gsub('old','new').gsub('foo','bar')

This is succinct and reads easily from left to right. This sort of procedure is where the classic object.method(arguments) syntax really shines. At least for me, it makes sense because it’s how I tend to think about a problem: “Take this object, do this to it, then do something else to it, then give me back the result.”

The trouble I have with prefix syntax is that it feels backwards. To read Lisp code, even my own, I have to dig through the parentheses to find the innermost expression, then work my way back out again. Of course, that’s basically what a Lisp interpreter or compiler does.

I like to think it would be possible to combine the flexibility of Lisp’s S-expressions with the left-to-write readability of object.method, but I don’t know what that would be. I have little experience with Forth-style postfix syntax, but it seems even less readable. But I think this just goes to show that syntax does matter.

5 Replies to “Chaining Function Calls”

  1. Why not just code up a CL procedure or macro (per your preference) so that you can do the following:

    (gsub string '(("this" "that") ("old" "new") ("foo" "bar")))

    This even manages to be a couple of characters shorter than the Ruby version, which is saying something.

    Shorthand like this should arguably already be provided by CL-PPCRE, but if it’s missing, it’s trivial enough to add – which is the whole point of Lisp, anyway. Any new abstraction you can imagine, you can build.

  2. Arto Bendiken Says:
    “Why not just code up a CL procedure or macro…”

    I thought about doing that, but never got around to it. Maybe I’m just lazy.

  3. Stuart Sierra wrote:

    “I like to think it would be possible to combine the flexibility of Lisp’s S-expressions with the left-to-write readability of object.method, but I don’t know what that would be.”

    Would putting the function in the last position of the S-exp instead of the first position do what you want? As in:

    (((string “this” “that” regex-replace-all) “old” “new” regex-replace-all) “foo” “bar” regex-replace-all)

  4. Kim Minh Kaplan wrote: “Would putting the function in the last position of the S-exp instead of the first position do what you want?”

    That’s an idea I’ve toyed with too, but I’m not sure how well it would work with macros. For example, if a macro wanted to add extra arguments to a function call, it would have to insert them before the last item in the list. Certainly doable, but less convenient than just sticking them on the end.

    And reversing the order of the S-exps wouldn’t help the readability of deeply-nested expressions. Indeed, it might make them harder to read. No, it’s an interesting solution, but not the one I’m looking for.

  5. This could use a lot of cleanup, but it’s one solution that actual looks pretty nice and makes the iterative process clear. If this were for production it would probably be a good idea to write the macro’s output as a series of functional compositions rather than wasteful assignments, but it was the interface that I was concerned with in this case:

    (defmacro with-default (starting-val &rest body)
    `(let ((_ ,starting-val))
    ,@(mapcar #'(lambda (line)
    `(setf _ ,line))
    body)))

    (with-default "this is a new foo"
    (regex-replace-all "this" _ "that")
    (regex-replace-all "old" _ "new")
    (regex-replace-all "foo" _ "bar"))

Comments are closed.