Let’s learn about a core feature of Elixir: it’s called the pipe operator. Then we’ll go over a real world example of how to utilize it.

When I decided to jump into Elixir, I had a dialog with a good german friend of mine (Robert if you’re reading, you’re awesome).

It went something like this:

- I’m going to work with Elixir.
- Elixir? …mkey…never heard of it.
- It’s a functional language that runs on the Erlang VM
- Functional? There will be a lot of piping involved.

Wise man.
There is indeed.

This is what it looks like:

|>

Time for some plumbing.

But actually it’s more like a factory.
Let me explain.

The pipe is used to make sequential transformations much more readable.

Imagine you need call a function with certain parameters you have, and use the output of that. Like so:

output1 = function(param)

Often you have to to this multiple times in sequence, making something like this :

output1 = function(param)
output2 = function2(output1)
output3 = function3(output2)
output4 = function4(output3)

Of course, one can simplify that in most languages to:

output4 = function4(function3(function2(function(param))))

Let me tell you, that’s not very readable, is it ?

What’s worse, you have to read this code from left to right then switch to right to left, and read it inside out.
That’s not the natural orders we humans read things.

We are simple beings.
We see code like this, we get confused.

Here is how you would do that in Elixir:

param
  |> function
  |> function2
  |> function3
  |> function4

Oh cool! Wait… what just happened?

It’s pretty straight-forward.

Whatever the output of the function being called, it will be passed to the next function as a argument.
And the argument of your first function is the thing on the top, in this case param.

So if we wanna break it down, for explanation sake, we could write it like:

output =
  param
    |> function

That’s the same as writing:

output = function(param)

For just 1 function like this, it probably doens’t make much sense to pipe.

Progressing to two function calls:

output2 =
  param
    |> function
    |> function2

The above is the same as:

output = function(param)
ouput2 = function2(output)

or more shortly:

ouput2 = function2(function(param))

So…

Hopefully you can see now how this:

output4 = function4(function3(function2(function(param))))

Is the same as the piped counterpart:

param
  |> function
  |> function2
  |> function3
  |> function4

And hopefully you can see how much more readable that is :)

But I need more Oomph!

“Great Leandro, but my real world problems don’t look like that.
Clearly my functions have several arguments, not just one.
How does your piping-schmiping handle that?

No sweat.
You can add arguments on the function calls.

Just remember, the first arguments is always the previous output.

So how would it look like? Let’s imagine that instead, you have these function calls:

output1 = function(param)
output2 = function2(output1, foo)
output3 = function3(output2, bar)
output4 = function4(output3, supa, dupa)

Or… more interestingly:

output4 = function4(function3(function2(function(param), foo), bar), supa, dupa)

(Do you even code readability brah?)

Now we cast the pipe spell on it:

param
  |> function
  |> function2(foo)
  |> function3(bar)
  |> function4(supa, dupa)

A Brief Conceptual Thought

Programming should be about transforming data.
Yes, you input stuff and you output stuff.

In, and out.
It’s simpler when we think about it this way, and it is what functional programming is all about.

Another way to imagine is that you have a series of transformations.

Another way to write the above code would be like:

param |> function |> function2(foo) |> function3(bar) |> function4(supa, dupa)

See ?
It’s like production line.

Real-world-shit time

Ok, now let’s actually achieve something other than raving about this.

Let’s say you have the following scenario: a list of emails, separated by commas.
And that you want to do the following to it:

  1. Force all emails to be lowercase
  2. Separate them into a list of strings
  3. Remove duplicates

Let’s quickly go over the Elixir functions we’ll need for this:


String.downcase/1

Naturally, turns a string into all lower case.

String.split/2

Separates the first argument into a list of strings, using the second argument as the separator.

Enum.uniq/1

Removes duplicates from an enumerable.

IO.inspect/2

Prints the value of the first argument, preceded by the second argument (like an identifier).
Also returns the first argument unmodified (so we can plug this between other calls without side effects).

This is used for debug purposes, production code shouldn’t have this.


Just in case you don’t know, Elixir signature of functions are defined only by their arity.
That is, the amount of arguments it receives.

Say the string we want to transform is in a variable named input:

input = "Example@Gmail.com,ExamplE@Gmail.COM,anoTHER.example@GMAIL.com"

Note the first two emails are duplicates.

Let’s start with:

input
  |> String.downcase

This will give us the lower case version.

And remember that calling input |> String.downcase is the same as calling String.downcase(input).
Let’s evolve it one more step.

input
  |> String.downcase
  |> String.split(",")

At this stage, the input will be lowercased and will be transformed into a list.

So, alas.

input
  |> String.downcase
  |> String.split(",")
  |> Enum.uniq

Now, we should have what we desire, with duplicates removed.
A technique you can use to debug it is to use the IO.inspect/2 mentioned before.

We can add a call in each of the steps and see exactly what’s going on:

input
  |> IO.inspect(label: "original")
  |> String.downcase
  |> IO.inspect(label: "downcase")
  |> String.split(",")
  |> IO.inspect(label: "split")
  |> Enum.uniq
  |> IO.inspect(label: "uniq")

original: “Example@Gmail.com,ExamplE@Gmail.COM,anoTHER.example@GMAIL.com”
downcase: “example@gmail.com,example@gmail.com,another.example@gmail.com”
split: [“example@gmail.com”, “example@gmail.com”, “another.example@gmail.com”]
uniq: [“example@gmail.com”, “another.example@gmail.com”]

P R O P E R L Y.

O N
A
D A I L Y.


That’s it for today, hope you enjoyed it.

Don’t forget to check out my latest posts:

And also to share this post with your friends and anyone interested in learning about this subject.
It’s only fair to share!

If this was useful to you,
please leave a comment and let me know!

If it wasn’t, also leave a comment and point out what a total crap and waste of your time it was.

Take care and until next time,
Happy brewing!