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 namecar
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.