The Pipe Operator
Pipe operator
The |>
operator is one of Elixir's most boasted features. It's known as the "pipe" operator, or "pipe forward" operator. It is essential to writing idiomatic Elixir. We'll start out by defining what the pipe operator is, followed by some examples.
The pipe operator works by using the return value from the expression on the left of the operator as the first argument to the function call on the right of the operator.
The pipe operator solves the issue of nested function calls. Let's start with a single function from the String module.
iex> String.capitalize("freddy")
"Freddy"
Here we are calling the "capitalize" function, passing the string, "freddy". This is straightforward. Let's add another step.
iex> String.reverse(String.capitalize("freddy"))
"ydderF"
Now we are reversing the string after we capitalize it. This is a little less straightforward because we naturally read from left to right but the functions are applied to the string from right to left. Let's add another step.
iex> String.ends_with?(String.reverse(String.capitalize("freddy")), "F")
true
First we capitalize the string, then we reverse it, finally we check if the string ends with a capital "F". This way of writing functions is very messy and difficult to read. We shouldn't be nesting the functions but rather creating pipelines to perform each transformation.
We know that Elixir will always return a value. Let's begin with our string, "freddy".
# step 0
iex> "freddy"
"freddy"
The string "freddy" is returned as the string "freddy". Ok, the next step is to capitalize our string.
# step 1
iex> "freddy" |> String.capitalize()
"Freddy"
So, what happened here? It looks as if we didn't pass any arguments to the capitalize
function. However, let's go over our definition of the pipe operator once more.
The way the pipe operator works is by using the return value from the expression on the left of the operator as the first argument to the function call on the right of the operator.
We know from "step 0" that the string "freddy" returns "freddy". Looking at "step 1", we can see that the pipe operator is taking the return value "freddy" and passing it as the first argument to the capitalize
function. As we can see the result of this is "Freddy". Let's add the next step.
# step 2
iex> "freddy" |> String.capitalize() |> String.reverse()
"ydderF"
We know from "step 1" that our return value is "Freddy", which will be the first argument passed to the reverse
function. As you can see, the pipe operator allows us to write our code in the order it should be performed. This makes reading the code much easier. Finally, we can check if our string ends with a capital "F". Here is the last step:
# step 3
iex> "freddy" |> String.capitalize() |> String.reverse() |> String.ends_with?("F")
true
Note: the function ends_with?
takes two arguments, the string and the character to check for. We only passed in the second argument, the letter "F". The first argument is passed in for us via the pipe operator, just like steps 1 and 2. Our code is on a single line now but in an Elixir file we would split the lines on each pipe operator for optimum readability.
# using pipelines
"freddy"
|> String.capitalize()
|> String.reverse()
|> String.ends_with?("F")
#=> true
# using nested functions
String.ends_with?(String.reverse(String.capitalize("freddy")), "F")
#=> true
Compare the code samples above. The both produce the same output, the boolean "true". The example using pipelines is the idiomatic way of writing and thinking in Elixir. Each pipeline performs the desired transformation and returns the result for the next pipeline. This code is easy to write, easy to read, and therefore easy to extend. The pipe operator is one of the core components in Elixir. You'll find it spread throughout every Elixir application.
Elixir is a functional language. This means that functions are the essence of Elixir. Ideally, functions should be succinct, decoupled, and have a single-purpose. This makes the code easy to test and easy to scale. A key component to writing functional code is thinking in terms of data transformations. Applications can be modeled as a serious of inputs and outputs. The pipe operator is one of the tools that Elixir developers can use to define these data transformations.