The Pipe Operator in Elixir |>

The Pipe Operator in Elixir |>

ยท

3 min read

Programming can end up becoming cluttered up. As functionality grows, the need to nest functions grows too. One of the features that make Elixir stand out is its pipe operator, denoted by |> . In this blog, we'll explore the pipe operator in Elixir and how it can make your code more readable and maintainable.

The pipe operator allows you to chain functions together, passing the output of one function as the first argument to the next function.

Suppose we have a list of numbers that we want to add together, double the result, and then convert it to a string. Here's how we could do that without using the pipe operator:

iex> numbers = [21, 42, 69, 99, 111]
[21, 42, 69, 99, 111]
iex> sum = Enum.sum(numbers)
342
iex> double = sum * 2
684
iex> result = Integer.to_string(double)
"684"

This code works, but it's not very readable. We have to keep track of multiple intermediate variables (sum, double, and result) and read the code from the inside out.

Now let's look at the same code using the pipe operator:

iex> numbers = [21, 42, 69, 99, 111]
[21, 42, 69, 99, 111]
iex> numbers |> Enum.sum() |> Kernel.*(2) |> Integer.to_string()
"684"

This code is much more concise and easier to read. The pipeline starts with the list of numbers and passes it to the Enum.sum function. The output of that function is then passed to the Kernel.*(2) function, which doubles the result. Finally, the output of that function is passed to Integer.to_string, which converts the result to a string. The result is a single expression that can be read from left to right.

Here's another example that shows how the pipe operator can be used with anonymous functions:

iex> "bruce banner" 
 ... |> String.split()
 ... |> Enum.map(&String.capitalize/1) 
 ... |> Enum.join(" ")
"Bruce Banner"

In this example, we use the String.split function to split a string into a list of words, then use Enum.map with an anonymous function to capitalize the first letter of each word, and finally use Enum.join to combine the words back into a single string.

The pipe operator can also be used with multiple arguments. In this case, you can include the function name and its arguments in parentheses:

iex> [1, 2, 3, 4] |> Enum.reduce(0, &+/2)
10

In this example, we use the Enum.reduce function to sum a list of numbers. The first argument to Enum.reduce is the initial value (0), and the second argument is the anonymous function that adds two numbers together.

The above example might seem basic however, the pipe operator can be used with even more complex functions.

defmodule PhoenixAuth.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  import Pbkdf2 #to hash password before it is saved

  @derive {Jason.Encoder, except: [:__meta__, :auth_tokens, :password]}

  schema "users" do
    field :email, :string
    field :password, :string
    field :username, :string
    timestamps()
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:username, :email, :password])
    |> validate_required([:username, :email, :password])
    |> validate_length(:username, min: 5, max: 25)
    |> validate_length(:password, min: 8, max: 30)
    |> unique_constraint(:email)
    |> unique_constraint(:username)
    |> update_change(:email, fn email -> String.downcase(email) end)
    |> update_change(:username, &(String.downcase(&1)))
    |> hash_password()
  end


  defp hash_password(changeset) do
    case changeset do
      %Ecto.Changeset{
        valid?: true, 
        changes: %{password: password}
    } -> change(changeset, 
                Pbkdf2.add_hash(password, hash_key: :password))
      _ -> changeset
    end
  end
end

In the above example, we can see that the pipe operator can be used to nest even more functions. The above is a schema module for a User struct which using Ecto will be saved to the users table in the database once migrations are made. A changeset is used to chain functions that will determine how an entity is saved.

In conclusion, the pipe operator is a powerful feature of Elixir that allows you to chain functions together in a concise and readable way. It can make your code more maintainable and easier to understand.

ย