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.

Note: The following examples assume that you .require the ["|" library/lux/control/pipe] module.

Piping macros in the standard library

Anyhow, after looking at some of the innovations of Clojure in 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 library/lux/control/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:

... Loops for pipes.
... Both the testing and calculating steps are pipes and must be given inside tuples.
(|> 1
    (|.loop [(n.< 10)]
            [++]))

loop 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 [(n.< 10)] is like (|> value (n.< 10)), and [++] is like (|> value ++).

Which value? Whatever has been piped into loop from the underlying |> (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
    (|.cond [i.even?] [(i.* 2)]
            [i.odd?]  [(i.* 3)]
            ... else branch
            [(|.new -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 new thing?

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

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

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

Useful in certain kinds of situations.


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

let binds the current value piped into it so you can refer to it multiple times within its body.

Pretty nifty, huh?


... Pattern-matching for pipes.
... The bodies of each branch are NOT pipes; just regular values.
(|> 5
    (|.when 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 step in the monadic computation is a pipe and must be given inside a tuple.
(|> 5
    (|.do identity.monad
          [(i.* 3)]
          [(i.+ 4)]
          [+]))

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

Piped monadic 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 let>:

(def .public let
  (syntax (_ [binding <code>.any
              body <code>.any
              prev <code>.any])
    (in (list (` (.let [(, binding) (, 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.

(only (|>> (member? forbidden_definitions)
           not)
      all_definitions)