Lisp – Chaining Operator

In the Javascript world, it is a common thing to chain methods call. For example, this could be a call from an image processing library.

Image('in.png')
  .resize(200, 100)
  .erode()
  .save('out.jpg');

In Lisp, there is not such thing as a dot notation to call an object method. Methods are functions taking the object as first argument. To mimic the dot operator that allows chaining we would like to write:

($ (image "in.png") ; Note: . is not a valid name, we use $ instead
   (resize 200 100)
   (erode)
   (save "out.jpg"))

Hopefully, Lisp allows to rewrite the previous snippet into code that actually works with macros.

With temporary variables

The first way to rewrite it is with a serie of assignement. It uses a temporary variable that is being passed along. progn is being used to group the actions into a single block that returns the tmp value.

(progn
  (defvar tmp (image "in.png"))
  (setf tmp (resize tmp 200 100))
  (setf tmp (erode tmp))
  (setf tmp (save tmp "out.jpg"))
  tmp)

And this is the macro that makes it work.

(defmacro $ (object &rest actions)
  (let ((curr-object (gensym)))
    (concatenate
     'list
     '(progn)
     (list `(defvar ,curr-object ,object))
     (loop for action in actions collect
           `(setf ,curr-object
                  (,(car action) ,curr-object ,@(cdr action))))
     (list `,curr-object))))

Some keys to understand it if you don't know lisp macros.

  • ` set the following as output code
  • , evaluate the code
  • @ expand the list. (resize @(200 100)) -> (resize 200 100)
  • gensym creates a local variable with a unique name
  • car is the first element of the list, cdr is the rest

Inline

The previous way was probably how would have written it in your code. Since we are programmaticaly rewriting the operation, we do not care about how readable the output is. We can remove the use of the temporary variable inlining the calls.

(save (erode (resize (image "in.png") 200 100)) "out.jpg")

The macros that powers it is much smaller.

(defmacro $ (object &rest actions)
  (let ((res `,object))
    (loop for action in actions do
          (setf res `(,(car action) ,res ,@(cdr action))))
    res))

Conclusion

I took a popular design pattern on the Javascript world and adapted it to lisp. It makes writing several chained method calls easier.

If you liked this article, you might be interested in my Twitter feed as well.
 
 

Related Posts

  • April 5, 2012 Climb – Property-based dispatch in functional languages (1)
    ELS Presentation | A Generic and Dynamic Approach to Image Processing | Chaining Operators & Component Trees | Property-based dispatch in functional languages This is the third (and last) presentation about my work on Climb at the LRDE. During the first one I tackled genericity […]
  • May 10, 2012 Generic Image Processing With Climb – 5th ELS (0)
    ELS Presentation | A Generic and Dynamic Approach to Image Processing | Chaining Operators & Component Trees | Property-based dispatch in functional languages Laurent Senta had the opportunity to go to the 5th European Lisp Symposium to present Climb, the project I've been […]
  • October 12, 2011 Intercept and alter <script> include (2)
    For a project, I want to transparently be able to intercept all the included javascript files, edit the AST and eval it. This way I can manipulate all the code of an application just by inserting a custom script. Hook the <script> tag insertion. Download the Javascript file […]
  • August 19, 2011 Javascript – Stupid Idea: Hoisting at the end (0)
    JSLint imposes us to do manual hoisting of variables. What if we did it but at the end of the function? :P How you write function print_array (array) { var length = array.length; for (var i = 0; i < length; ++i) { var elem = array[i]; console.log(elem); […]
  • August 23, 2011 Javascript – Hook Technique (5)
    Let's go back 5 years ago during the World of Warcraft beta. I was working on Cosmos UI, a projects that aimed to improve the World of Warcraft interface. As interface modification was not officially supported by Blizzard, we went ahead and directly modify the game files written in […]