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