Reputation: 43
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?
Upvotes: 4
Views: 382
Reputation: 71119
how about
(reduce (lambda (a b) (funcall b a))
(list thing
(partial-apply op1 arg1)
(partial-apply op2 arg2)
...
(partial-apply opn argn) ))
(in Common Lisp). In Racket,
(foldl (lambda (a b) (a b))
thing (list
(partial-apply op1 arg1)
(partial-apply op2 arg2)
...
(partial-apply opn argn) ))
Regarding terminology, it's either ((curry fun) arg)
or (partial-apply fun arg)
.
Upvotes: 0
Reputation: 139401
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
Upvotes: 10
Reputation: 223153
Clojure has a threading operator, ->
, which does what you expect:
(-> thing
(thing-operation1 extra-arg1)
(thing-operation2 extra-arg2)
(thing-operation3 extra-arg3))
You can implement this easily as a macro in other Lisp dialects. Greg Hendershott's rackjure library has a ~>
form that does the same thing in Racket, for example.
The ->
(or ~>
in rackjure) macro splices the result in as the first argument of each subform. If you want to splice the result in as the last argument instead, there's a ->>
macro (~>>
in rackjure).
Upvotes: 3
Reputation: 1
You might use the PROGN Common Lisp special form.
Or you could define your own Lisp macro to fit your taste.
Upvotes: 0