The pipe operator (|>) is one of the most powerful and elegant features of Elixir
What is the pipe operator?
The pipe operator (|>) takes the result of the expression on the left and passes it as the first argument to the function on the right. This allows you to create somewhat of a pipeline, which, in my opinion, makes your code easier to follow and read.
Example
You'll most commonly find the pipe operator (|>) used in list processing tasks. Let's walk through a very simple example.
Given a list of integers, [1, 2, 3, 4, 5]
, you want to filter out even numbers, multiply the remainig ones by 5, and then returning the sum (can't get any more real world than that 😌).
There a many different valid ways to do this. Let's explore a few.
Option 1: The ancient philosophers
As an apprentice working for the Emperor, you might decide to use the Enum module to do the task like so:
elixir
@spec manipulator(list(integer())) :: integer()
def manipulator(list) do
odd_list = Enum.filter(list, fn x -> rem(x, 2) != 0 end)
new_list = Enum.map(odd_list, fn x -> x * 5 end)
Enum.sum(new_list)
end
manipulator([1, 2, 3, 4, 5]) # Returns 45
This, of course, will run as expected
Option 2: Total anarchy
This is no different from the Option 1, only that it's shorter and harder to read.
elixir
@spec manipulator(list(integer())) :: integer()
def manipulator(list) do
Enum.sum(
Enum.map(
Enum.filter(list, fn x -> rem(x, 2) != 0 end),
fn x -> x * 5 end))
end
manipulator([1, 2, 3, 4, 5]) # Returns 45
Total anarchy right?
Option 3: The pipe operator
Now, as a seasoned engineer in the modern world (of course you time jumped from the Emperor's era), you decide to use the pipe operator (|>).
elixir
@spec manipulator(list(integer())) :: integer()
def manipulator(list) do
list
|> Enum.filter(fn x -> rem(x, 2) != 0 end)
|> Enum.map(fn x -> x * 5 end)
|> Enum.sum()
# Or you can use the anonymous function shorthand
list
|> Enum.filter(&(rem(&1, 2) != 0))
|> Enum.map(&(&1 * 5))
|> Enum.sum()
end
manipulator([1, 2, 3, 4, 5]) # Returns 45
For more info on anonymous functions, check out the well written documentation.
Look at that. Pure joy isn't it. It clearly shows the "flow of data".
list
is passed to Enum.filter- The result of which is passed to Enum.map
- Then finally to Enum.sum
Tips and tricks
Here are a few tips and tricks you should keep in mind while using your new superpower.
Keep it simple
Don't overuse the pipe operator (|>), like where a single function call is used:
elixir
# Instead of:
[1, 2, 3] |> Enum.sum()
# Do:
Enum.sum([1, 2, 3])
Consistent formatting
Align the (|>) on the same 'column' to increase readability:
elixir
# Instead of:
[1, 2, 3] |> Enum.map(&(&1 * 2))
|> Enum.sum()
# Do:
[1, 2, 3]
|> Enum.map(&(&1 * 2))
|> Enum.sum()
Debugging
Insert IO.inspect/2
in the pipeline to debug intermediate values.
elixir
list
|> Enum.filter(&(rem(&1, 2) != 0))
|> IO.inspect(label: "After filter")
|> Enum.map(&(&1 * 5))
|> IO.inspect(label: "After map")
|> Enum.sum()
Conclusion
As you can see, hopefully, the pipe operator (|>) is quite a powerfull tool for writing clean, readable and maintainable code. By chaining functions together, you can see how data flows and is transformed.
Whether you're manupulating lists, strings or maps, the pipe operator (|>) will make your code more elegant.
Happy piping! 🚀.