Appendix D: The Art of Piping

I'm a big fan of piping.

No kidding.

Piping is my favorite way of writing code.

It's almost like a game to me.

I try to figure out ways to get my code to be more pipe-sensitive, to see how far I can get while piping my code.

My personal record is 14 steps.

Anyhow, after looking at some of the innovations in Clojure on the piping department, I decided to come up with my own tricks to try to get Lux to become a piping superpower.

I added the lux/pipe module, which contains several macros meant to be used within the |> macro, and which extend it with awesome capabilities

Take a look at these babies:

Piping macros in the standard library

## Loops for pipes.
## Both the testing and calculating steps are pipes and must be given inside tuples.
(|> 1
    (!> [(i.< 10)]
        [i.inc]))

!> takes a test tuple and a body tuple.

The reason is that each of those tuples represents the steps on an implicit piping macro (oh, yeah!).

So [(i.< 10)] is like (|> value (i.< 10)), and [i.inc] is like (|> value i.inc).

Which value? Whatever has been piped into !> from the underlying |> call (in this case, the value 1).


## Branching for pipes.
## Both the tests and the bodies are piped-code, and must be given inside a tuple.
## If a last else-pipe isn't given, the piped-argument will be used instead.
(|> 5
    (?> [i.even?] [(i.* 2)]
        [i.odd?]  [(i.* 3)]
        [(_> -1)]))

We have looping, and now we have branching; with a cond-inspired piping macro (complete with else branch, just in case).

But what's that thing over there? That _> thing?

Well, it's another piping macro. Of course!

## Ignores the piped argument, and begins a new pipe.
(|> 20
    (i.* 3)
    (i.+ 4)
    (_> 0 i.inc))

_> establishes a new piping sequence that ignores any previous one.

Useful in certain kinds of situations.


## Gives the name '@' to the piped-argument, within the given expression.
(|> 5
    (@> (+ @ @)))

@> binds the current value piped into it so you can refer to it multiple times within it's body.

Pretty nifty, huh?

## Pattern-matching for pipes.
## The bodies of each branch are NOT pipes; just regular values.
(|> 5
    (case> 0 "zero"
           1 "one"
           2 "two"
           3 "three"
           4 "four"
           5 "five"
           6 "six"
           7 "seven"
           8 "eight"
           9 "nine"
           _ "???"))

Yeah, that's right!

I just couldn't resist rolling full-blown pattern-matching into this.

You'll thank me later.


## Monadic pipes.
## Each steps in the monadic computation is a pipe and must be given inside a tuple.
(|> 5
    (%> Monad<Identity>
        [(i.* 3)]
        [(i.+ 4)]
        [i.inc]))

And just to show you I'm serious, I did the unthinkable.

Piped macro expressions!

How to make your own piping macros

They're easier to make than pattern-matching macros.

All you need is a macro that takes anything you want as parameters, but always takes as its last argument the computation so far, as it has been constructed by the |> macro prior to the call to your piping macro.

As an example, here's the definition for @>:

(syntax: #export (@> [body s;any]
                     prev)
  (wrap (list (` (let% [(~' @) (~ prev)]
                   (~ body))))))

All this looks like madness, but I just couldn't contain myself.

Piping is one of the few ways of writing code that just amuses me whenever I do it.

These macros can keep you in the flow while you're writing complex code, so you don't have to switch so much between piping-code and non-piping-code.

Oh... and did I mention the |>. macro?

It generates for you a single-argument function that will immediately pipe its argument through all the steps you give it?

(filter (|>. (member? forbidden-defs) not)
        all-defs)

Yeah. This is real!

results matching ""

    No results matching ""