Getting Started with Elixir - Pipe Operator

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.
Getting Started with Elixir - Pipe Operator

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 to String.downcase.
  • The return value of String.downcase is passed to String.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.

Troy Martin
Senior Engineer working with Elixir and JS.
Keep in the Loop

Subscribe for Updates