In the last section we covered Elixir Control Flow expressions. Here we’re going to discuss how using the Pipe Operator can improve code readability and simplify complex data transformations in Elixir functions.
Elixir is a functional programming language and a large part of the philosophy is defining small and concise functions.
The pipe operator (|>
) takes the return value from the expression on its left-hand side and passes it as the first argument to the function call on its right-hand side, similar to the | operator in Unix.
Versions
- Elixir 1.10.0
Purpose
With functional programming a large part of program execution consists of passing data to functions which transform the data and return a new value.
This can quickly become difficult to read and understand when there are many transformations happening to a piece of data.
Example
Here’s a contrived example of working with a name to create a URL safe username:
iex> name = " John Smythe"
iex> String.trim(name)
"John Smythe"
The input we receive needs to be transformed for use in our program.
In this case we’re trimming whitespace from the name.
But we also need to downcase the characters:
iex> String.downcase(String.trim(name))
"john smythe"
And replace any literal spaces with a dash (-):
iex> String.replace(String.downcase(String.trim(name)), " ", "-")
"john-smythe"
As you can see, the code is becoming difficult to read and understand.
Refactor with the Pipe Operator
As previously mentioned, the pipe operator allows us to pass the return value of one expression as the first argument to another.
Let’s refactor our example using the Pipe Operator:
iex> name = " john smythe"
iex> name |> String.trim() |> String.downcase() |> String.replace(" ", "-")
"john-smythe"
And to break this down:
- Binding a value to name is the first expression, so the return value (“ john smythe”) is passed to String.trim which accepts one argument.
-
The return value of
String.trim
is passed toString.downcase
. -
The return value of
String.downcase
is passed toString.replace
as the first argument, and so we only need to pass the last 2 arguments. - Much easier to read, understand and maintain. If you wanted to add further transformations, you’d just need to pipe the last value into another function.
One-per-line
Piping function calls are often done one-per-line for even further readability:
name
|> String.trim()
|> String.downcase()
|> String.replace(" ", "-")
"john-smythe"
And the final return value can be bound to a new variable:
username = name
|> String.trim()
|> String.downcase()
|> String.replace(" ", "-")
iex> username
"john-smythe"
Wrapping Up
The Pipe Operator is a powerful tool to have in your Elixir tool-belt.
We’ve used a contrived example to show how you can pipe a value through multiple transformations with the benefits of keeping code clean and maintainable.