Charlie Harvey

The pipeline operator

One of the cool things about the Elixir language is that you can string functions together from left to right using the so-called pipeline operator |>. I was thinking about it this morning after reading this comment from taylorfausak on reddit.

Why flip everything around like that? Write expressions that read left to right like people are used to.

ns |> filter even |> map (^ 2) |> sum

The pipeline operator works a bit like the unix pipe operator. Here is a description from Dave Thomas:

The pipeline operator looks like magic, but it’s actually quite simple. It takes the value of the expression on its left, and inserts it as the first argument of the function call on its right, shifting all the other arguments down.

Or more briefly, it is just like function application in reverse.

I was surprised that my first try at implementing it worked straight away, I simply wrote

let (|>) x f = f x > :t (|>) (|>) :: a -> (a -> c) -> c

So that was cool. But recall that the pipeline is just function application in reverse. In Haskell we write $ for function application. We can simply use flip to get an implementation of |>.

> :t flip ($) flip ($) :: a -> (a -> c) -> c

As it turns out Data.Function has an implementation of the pipeline operator, but calls it & rather than |>. As an old time Unix person that doesn’t seem as obvious to me, but there you go.

Another operator that we might want is the reverse composition operator (>>) which F# has. In that language, they call it the forward composition operator — I just call it reverse because it is the reverse of (.)! And because Haskell already has a >> operator for monadic sequencing, I call it >>>.

Here’s the idea. We would like to be able to write a function for our original pipeline code from the reddit thread.

ns |> filter even |> map (^ 2) |> sum

Of course we could just write

fn ns = ns |> filter even |> map (^ 2) |> sum

But it would be nicer to write it pointfree, thus

fn = filter even >>> map (^ 2) >>> sum

That should work just the same. We can write either

> let (>>>) f g x = g (f x) > :t (>>>) (>>>) :: (t2 -> t1) -> (t1 -> t) -> t2 -> t

or, perhaps more simply

> let (>>>) = flip (.) > :t (>>>) (>>>) :: (a -> b) -> (b -> c) -> a -> c

And indeed it all works as expected

> ns [1,2,3,4,5] > sum . map (^2) $ filter even ns 20 > ns |> filter even |> map (^2) |> sum 20 > let fn = filter even >> map (^2) >> sum > ns |> fn 20


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.