alols
alols

Reputation: 43

Sequential procedures in Lisp

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

Answers (4)

Will Ness
Will Ness

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

Rainer Joswig
Rainer Joswig

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

C. K. Young
C. K. Young

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

You might use the PROGN Common Lisp special form.

Or you could define your own Lisp macro to fit your taste.

Upvotes: 0

Related Questions