Wednesday, May 6, 2015

>> Imitation Forward Pipe for Common Lisp

Since learning a little F# about a year ago, I took a liking to its forward pipe operator (|>). The semantics of the forward pipe work great with F#'s sequence data type (with so-called, "lazy" computation) and functional style programming. It allows you to express a progression of consecutive steps in the order they will be done, while allowing you to directly feed the results of the last function into the input of the next function, avoiding redundant variable declarations. I suppose you could call these types of consecutive actions "transitive" combinations of operations. Imperative style programming would preserve the apparent sequence of events (meaning, the order you type the operations in, is the order the operations happen in), but it also involves declaring intermediate variables, which perhaps you will only use once.

I'm not an expert on functional style programming and don't intend, right now, to explain or justify its merits, except to say that sometimes it produces cleaner, clearer code. But while I have a current preference for Common Lisp above most other (programming) languages at the present, I really like that forward pipe operator in F#. This morning, I thought it was time to cross that bridge—try to make an imitation forward pipe in Common Lisp.

In F#, if you wanted to turn a into b into c, you might do something like

let a2c x =
    a2b x
    |> b2c

where b2c also takes one parameter. In Lisp, you end up with something more like

(defun a->c (x)
  (b->c (a->b x)))

This is fine for being concise but it doesn't preserve the (apparent) logical ordering of \(a\to b\to c\). We could do that with a change to something more imperative, such as

(defun a->c (x)
  (let ((b (a->b x)))
    (b->c b)))

and this works okay. However, I would like to write

(defun a->c (x)
  (>> 
    (a->b x)
    (b->c)))

Or,

(defun a->e (x)
  (>> :pipe-in
    (a->b x)
    (b->c)
    (c->d :pipe-in 0.0625d0)
    (d->e)))

where we want this last one to expand into

(D->E (C->D (B->C (A->B X)) 0.0625))

I've introduced a keyword so that we can dictate which parameter of the next function the previous result goes into. You can specify any keyword to use as a pipe-in parameter, but make sure you don't use a keyword of one of the functions being called in the sequence—it'll really mess with your mind. Here's the macro definition along with an error condition:



To test that the code works, we can try this at the REPL,

CL-USER> (macroexpand-1 '(>> :pipe-in
 (a->b x)
 (b->c) 
 (c->d :pipe-in 0.0625) 
 (d->e)))
(D->E (C->D (B->C (A->B X)) 0.0625))

This macro does not attempt to handle multiple return values—though such a development might lead to an interesting result. It might be as easy as introducing more keywords.

No comments: