When I try to program in a functional style with immutable objects, sequential operations end up being written inside-out, like this:
(thing-operation3
(thing-operation2
(thing-operation1 thing extra-arg1)
extra-arg2)
extra-arg3)
I'm starting to see this pattern repeating all over my code, and I find it very hard to read. This could marginally be improved using higher-order procedures like curry and compose:
((compose1
(curryr thing-operation3 extra-arg3)
(curryr thing-operation2 extra-arg2)
(curryr thing-operation1 extra-arg1))
thing)
Better perhaps, but it is still written upside-down, and it takes some extra cognitive load to figure out what is going on. And I'm not sure whether this is ideomatic Lisp-code.
Object-oriented style is so much easier to read:
thing.operation1(extra-arg1).operation2(extra-arg2)
.operation3(extra-arg3)
It reads in a natural order, and it could also be implemented with immutable objects.
What is the ideomatic way of writing such sequential operations in Lisp so that they are easy to read?
An usual way in Common Lisp would be to use LET*
(let* ((thing1 (thing-operation0 thing0 extra-arg0))
(thing2 (thing-operation1 thing1 extra-arg1))
(thing3 (thing-operation2 thing2 extra-arg2)))
(thing-operation3 thing3 extra-arg3))
That way one can name the return values, which improves readability and one could write declarations for those.
One could also write a macro which might be used like in the following:
(pipe
(thing-operation1 thing extra-arg1)
(thing-operation2 _2 extra-arg2)
(thing-operation3 _3 extra-arg3)
(thing-operation4 _4 extra-arg4))
Some language provide similar macros and Lisp libraries may provide variations of it. Let's write a simple version of it:
(defmacro pipe (expression &rest expressions)
(if (null expressions)
expression
(destructuring-bind ((fn arg &rest args) &rest more-expressions)
expressions
(declare (ignorable arg))
`(pipe
(,fn ,expression ,@args)
,@more-expressions))))
For above pipe
expression the following code is produced:
(THING-OPERATION4
(THING-OPERATION3
(THING-OPERATION2
(THING-OPERATION1 THING EXTRA-ARG1)
EXTRA-ARG2)
EXTRA-ARG3)
EXTRA-ARG4)
A variant:
(defmacro pipe (expression &rest expressions)
(if (null expressions)
expression
(destructuring-bind ((fn arg &rest args) &rest more-expressions)
expressions
`(pipe
(let ((,arg ,expression))
(,fn ,arg ,@args))
,@more-expressions))))
This would let you write:
(pipe (+ 1000 pi)
(+ arg1 arg1) ; use the previous result multiple times
(+ arg2 (sqrt arg2))) ; use the previous result multiple times
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With