4

Is there a ready made lisp macro that allows chaining (piping) of functions? I couldn't find one. I'll try to explain what I mean with this example.

Instead of using let* with lots of unused intermediate variables like this:

(let*
  ((var1 (f1 x y))
   (var2 (f2 x var1))
   (var3 (f1 var2 z)))
 var3)

I would like to have it written like this:

(->
  (f1 x y)
  (f2 x _)
  (f1 _ z))

where, obviously _ will be return value from previous expression. A plus is if would be possible to use _1, _2, ... to reference previously returned values.

This is the idea, exact syntax is not that important.

I know this is not that hard to write, but seems so useful that it has to be written already.

Marko
  • 30,263
  • 18
  • 74
  • 108
  • Out of sheer curiosity, did you end up using this code regularly? It often seems more readable to (f1 (f2 x (f1 x y)) z) instead of the cascade of let*. – Paul Nathan Nov 22 '11 at 22:15

5 Answers5

7

Something like this?

(defun chain-expander (forms)
  (cond ((null (cdr forms)) (car forms))
    (t `(let ((it ,(car forms)))
          ,(chain-expander (cdr forms))))))

(defun chain-counted-expander (forms counter)
  (cond ((null (cdr forms)) (car forms))
    (t (let* ((name (format nil "_~d" counter))
          (anaphora (or (find-symbol name) (intern name))))
         `(let ((,anaphora ,(car forms)))
        ,(chain-counted-expander (cdr forms) (1+ counter)))))))

(defmacro chain (&body forms)
  (chain-expander forms))

If you'd prefer something where _1, _2, and so on is usable, simply replace the call to CHAIN-EXPANDER with a call to CHAIN-COUNTED-EXPANDER (with your preferred first number, I'd suggest either 0 or 1). Note that it explicitly only caters to using _N as a reference, but changing it so that it also binds _ for each subsequent level is not very hard.

Marko
  • 30,263
  • 18
  • 74
  • 108
Vatine
  • 20,782
  • 4
  • 54
  • 70
5

Why not just

(f1 (f2 x (f1 x y)) z)

?

Or make that into a function ?

Christoffer Hammarström
  • 27,242
  • 4
  • 49
  • 58
  • Well, the second approach seems more readable, especially with longer function sequences. – Marko Jan 29 '10 at 15:21
  • actually I find this more understandable. With the piping version I try to reconstruct the flow of values in my mind. The version here makes it clearer. The piping version requires an 'imperative' mindset. – Rainer Joswig Feb 02 '10 at 10:28
2

You could use the ablock macro from On Lisp

(defmacro alambda (parms &body body)
  `(labels ((self ,parms ,@body))
     #'self))

(defmacro ablock (tag &rest args)
  `(block ,tag
     ,(funcall (alambda (args)
            (case (length args)
              (0 nil)    
          (1 (car args))
          (t `(let ((it ,(car args)))    ;; change it to _ 
            ,(self (cdr args))))))
       args)))

The macro binds the value of the previous expression to 'it', but you can change that to '_' if you want. Of course, you can also change the name to -> or whatever else you feel like.

Erik Haliewicz
  • 126
  • 1
  • 4
1

via On Lisp

grettke
  • 1,407
  • 7
  • 10
0

You may find this question interesting: Tacit programming in Lisp

My answer to that question is similar to Vatine's reply to this question, but without the counting; as a side note, I think the counting is dangerously fragile - you can introduce reordering bugs when you update the code. It's probably better to supply a way to name the previous results - but in practice, I've seldom needed results other than the last one. If you need them, maybe you should write a function instead of a chaining expression.

Community
  • 1
  • 1
Miron Brezuleanu
  • 2,989
  • 2
  • 22
  • 33