I'm a computational neuroscientist by trade and the language of choice in our lab is Matlab. Despite being occasionally bizarre, and more frequently ad-hoc, and despite lacking some of the features a real language connoisseur might like (higher order functions, closures, some kind of class system that isn't wonky, real anonymous functions), Matlab is very good at what it does, which is manipulate vectors and matrices. Since I spend so much time writing code in Matlab, I frequently end up trying to write Matlab idioms in other languages. For those not familiar with Matlab's style, the main thing is that most functions are "vectorized", which means they automatically map over vector inputs, usually in a smart way. Most of the time, though, you are just doing simple things, like

>> x = [1 2 3 4 5];
>> x.^2
ans =
[1 4 9 16 25]
>>

The most direct way to translate this into Common Lisp is:

CL-USER> (defun ^ (b e) (expt b e))
^
CL-USER> (let ((x #(1 2 3 4 5)))
(map
  'vector
  (lambda (b) (^ b 2.0))
  x))
#(1.0 4.0 9.0 16.0 25.0)
CL-USER>

Which leaves something to be desired, since in Matlab style code, you might have several such vectorized operations on a single line, chained. I've encountered more than one Common Lisper who is suspicious of brevity for the sake of brevity, but the major advantage of Lisp is supposed to be that you grow your language up to meet your problem. Today, at least for pedagogical purposes, our problem is we want to make Common Lisp act a bit more like Matlab. And we are going to do it with reader macros, since our major aim is to express as concisely as possible vector operations. (N.B. - I am no Common Lisp master - there may be conceptual or other errors, but the code at least works!). The first thing is to recognize the pattern we want to abstract away. A vectorial function applications looks like:

(map 'vector *F* *V1* .. *VN*)

What we are going to do is replace this with the following syntax:

#v(*F* *V* .. *VN*)

Which will mean "give me a vector produced by applying *F* to *V1* through *VN*, where *F* has arity N". The CLHS defines, with characteristic obliqueness, a Reader Macro thusly: reader macro n.
  1. a textual notation introduced by dispatch on one or two characters that defines special-purpose syntax for use by the Lisp reader, and that is implemented by a reader macro function. See Section 2.2 (Reader Algorithm).
  2. the character or characters that introduce a reader macro[1]; that is, a macro character or the conceptual pairing of a dispatching macro character and the character that follows it. (A reader macro is not a kind of macro.)
I can only hope that the author intended to separate the subsurreal caveat "(A reader macro is not a kind of macro)" from 2, where it does not seem to belong. The main idea here is that we are extending Common Lisp's Reader - the algorithm that takes text and converts it into data/code for the Lisp Compiler/Interpreter to work with. A reader macro takes one or two characters and sets them up to trigger a function call during the read process wherein user defined code can grab things from the input stream and transform them into an object to give to the Reader. We want to cause the #v character to trigger a function which reads an s-expression from the input, and converts (f v1 .. vn) to (map 'vector (function f) v1 .. vn). We inform Lisp of our intent using the set-dispatch-macro-character function.

(set-dispatch-macro-character
  #\# #\v
  #'vector-map-transformer)

This tells the Reader to call VECTOR-MAP-TRANSFORMER whenever it see the pair of characters #v. All that remains is to define VECTOR-MAP-TRANSFORMER:

(defun vector-map-transformer
  (stream subchar arg)
    (let* ((sexp (read stream t))
           (fname (car sexp))
           (arguments (cdr sexp)))
     `(map 'vector
           (function ,fname)
           ,@arguments)))

The reader expects a function with three arguments, stream, which will be bound to the current input stream, subchar, which is the subchar which triggered the call (in this case \v) and args, which we wont be using, but represent arguments passed to the Reader Macro. The body of vector-map-transformer is pretty straightforward. It extracts the function name from the sexp read from the input stream, grabs the cdr of the sexp to get the argument list, and uses back-quote notation to build a new list which will passed back to the reader. Note we use the function function to get the function bound to fname. This way we don't have to use a pound-quote when typing in vector functions. With these two statements, we can now invoke our reader macro at the REPL.

CL-USER> #v((lambda (x) (+ x 1))
'(1 2 3 4))
#(2 3 4 5)

Sweet. Still, I find that lambda form a bit clunky. And we are always defining small lambdas to map across bodies. Let's create another Reader Macro to expand [x y z -> (+ x y z)] to (lambda (x y z) (+ x y z)). In this case we are going to use the #\[ character as the trigger for our macro. And since we want to stop reading at the #\] character, rather than at an s-expression, we have to take extra care to make sure Lisp is up with everything we want. Instead of using set-dispatch-macro-character we use set-macro-characterthe major difference being that this Reader Macro is just one character, so we pass it a function with only two arguments, instead of three. There is no subchar.

(set-macro-character #\[
(lambda (stream char)
 (let* ((lst
        (read-delimited-list #\]
     stream t)))
  (short-lambda-split lst))))
The function short-lambda-split does most of the lifting in this Macro. Note, though, that instead of using read we use read-delimited-list and terminate the read on the character #\]. This gives us the body of the short lambda form. short-lambda-split then converts this into the sexp to return to the reader:

(defun short-lambda-split (elements)
(let ((args
(loop while (and
       (not
   (eq (car elements) '->))
       elements)
collect
(prog1
   (car elements)
   (setf elements (cdr elements))))))
(if (not elements)
; There is no arg list
  `(lambda (x) ,@args)
  `(lambda (,@args) ,@(cdr elements)))))
This function basically takes the list, finds the -> symbol which separates the argument list from the body and then constructs the lambda expression. If there is no -> the function assumes one argument, named x and that the entire list passed in represents the body of the function. We can write an arity one short lambda like [(+ x 1)] without specifying the lambda argument list. Before we can use the short-lambda reader macro we have to do something about the #\] characters polluting the input stream or they will confuse the reader. The solution is to tell lisp to treat them like #\) (closing parenthesis) characters so that it won't complain (at least I believe this is what is going on). We do that this way:

(set-macro-character #\] 
  (get-macro-character #\)))
We can now use our new short lambda in conjunction with our vector application operator to write some very concise code for vector operations.

CL-USER> (let ((x #(1 2 3 4 5)))
#v([b -> (^ b 2.0)] x))
#(1.0 4.0 9.0 16.0 25.0)
CL-USER>
A word of caution: Macros are always tricky beasts, and extending Lisp this way may only seem like a good idea. In practice, it introduces all sorts of tricky issues. In addition to the regular issues of macro expansion, most of which apply more or less to reader macros, adding new forms to the language confuses tools which might be trying to understand Lisp syntax, like Emacs. I also find that my Lisp (SBCL) can sometimes return confusing errors when reader Macros are in use. So use this information wisely. I nevertheless hope this little tutorial has helped clarify a very interesting ability of Common Lisp. Using these macros, vectorized numerical code is much shorter and easier to understand. I used these macros to write some code for a class I TA where Matlab is the language of choice. Most of the time, these simple extensions put the Common Lisp implementation on the same footing, brevity-wise, as Matlab and I got to program in Lisp, which is a lot more fun. Comments and questions welcome. PS - I apologize for any confusing indentation - Blogger is being very disrespectful with my pre and code tags.
10

View comments

  1. Fun stuff.

    Ideally #v should probably just be a higher order function.
    (defun v (fn &rest args)
    (apply #'map 'vector fn args))

    This is shorter to type, more lispy (you're not inventing new syntax),will respect packages and such like, and lets you use v as a fn. so you can write:
    (v #'v fns list1 list2)
    and generate code that does odd things to vectors of vectors.

    I'm afraid I couldn't actually read the second half of your post, as the code font is to big and overflows your column margins in firefox 3.

    ReplyDelete
  2. Excellent, dude! You've got a real talent for explaining hard stuff. Looking forward to more CL explanations from you!

    Many thanks.

    ReplyDelete
  3. You are almost certainly right about V being a higher order function - as stated above, the big purpose is illustrative rather than functional (no pun intended).

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. Nice article. The comment spam kind of detracts, but I've been trying to figure out what reader macros are about for quite a while now. Got a serious "whoa!" Keanu moment there with the [] syntax. Thank you.

    ReplyDelete
  9. James, thanks for the reminder to clean up the comment spam! Now that I look at my analytics, this article is by far the most popular thing I've ever written. I should clean up.

    I'm glad you've found it useful.

    ReplyDelete
  10. Another thank you. What a great, not-contrived example of usage. Those are the best.

    ReplyDelete

As if rot and verdure weren't enough,
I then became the mythical man moth,
and I fluttered around, that summer, not moon,
but incandescent after incandescent,
each mellifluous orbs in the warm night,
each thrumming with the wings of my cohorts,
all in paroxysm for those faux-moons,
encased in glass, or perspex, and humming
with their own dead, but electric, heartbeat.
2

View comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. Woops. I posted and got two copies, then removed one and they both vanished. What I said was, "Serendipitously (as I suppose), I just posted about MMM to a private mailing list."

    ReplyDelete
Blog Archive
Have .emacs, will travel.
Have .emacs, will travel.
My Photo
So I'm a Common Lisp programmer trying to make good on my feeling that a human should be well rounded.
Loading
Dynamic Views theme. Powered by Blogger. Report Abuse.