Pipe Operator in Elixir

More from Author
Krupananda
Krupananda

Senior Software Engineer

Tags:
Elixir
4 min read

Elixir has become one of the most loved languages. Elixir applications are scalable, very reliable, and the syntax is also quite nice. In this article, we will discuss about how elixir pipe operator (|>) works

When we use functional programming languages, sometimes code becomes messy. Let's take a look at this code, this function returns the average height of the adults in Delhi state

Copy Code
        
  calculate_avg_height(adults(state('New Delhi')))
        
      

We can rewrite this code using the pipe operator

Copy Code
        
  'New Delhi' |> state |> adults |> calculate_avg_height
        
      

Pipe operator makes our lives easier and makes our code looks beautiful. In this section, we build our own pipe operator with <|> this symbol. Rules of pipe operator are simple, the result of one function passes as an argument to the next function.

Let's say we need to perform a series of addition and multiplication operations. we can simply use a pipe operator

Copy Code
        
  1  |>  add(2)  |>  multiply(5) |> div(15) 
        
      

In the first stage, 1 will be passed as an argument to add function

In second stage 1 and 2 are added and pass 3 as an argument to multiply the function

Multiply function performs 3 * 5 and pass 15 to div function and return 1 as result

Before we jump into building pipe operator. We need to have knowledge about elixir macros. You can learn about Elixir macros in this article

We use macros to build pipe operator. This basic difference between functions and macros is macros accept representation of code but functions accepts evaluation of code

We use 1 |> add(2) |> multiply(5) |> div(15) expression as reference to build pipe operator

Let's define a pipe macro. It will do pattern matching on the left and right arguments

Copy Code
        
  defmodule Pipe do
    defmacro left <|> right do
    end
  end
        
      

Define the unpipe function to pluck and store all the functions in array. You noticed that we store all the functions in AbstractSyntaxTree (AST) format

Define the unpipe function to pluck and store all the functions in
                    array. You noticed that we store all the functions in
                    AbstractSyntaxTree (AST) format
Copy Code
        
  def unpipe(expr) do
    Enum.reverse(unpipe(expr, []))
  end

  defp unpipe({:<|>, _, [left, right]}, acc) do
    unpipe(right, unpipe(left, acc))
  end

  defp unpipe(other, acc) do
    [{other, 0} | acc]
  end
        
      

We need to define more function to pipe all the functions into a single function. You can picture the process by observing the following diagram

In the first stage we will add 1 as an argument to quoted expression {: add,_, [2]} In next stage we add {:add,_, [1, 2]} as an argument to {:multiply _, [3]} and so on

elixir inbuilt function List

We use elixir inbuilt function List.foldl function to perform pipe operation and return the expression {:div, _, [{:multiply, _, [{:add, _, [1,2],3]},15]} to the caller

Copy Code
        
  [{h, _} | t] = unpipe({:<|>, [], [left, right]})
  value  =  List.foldl(t,h , fn {x,pos},acc -> pipe(acc,x,pos) end)
        
      
Copy Code
        
  def pipe(expr, {call, line, _}, integer) do
    {call, line, List.insert_at([], integer, expr)}
  end
        
      

The resulting module will look like this.

Copy Code
        
  # pipe.ex
  defmodule Pipe do

    defmacro left <|> right do
      [{h, _} | t] = unpipe({:<|>, [], [left, right]})
      List.foldl(t,h , fn {x,pos},acc -> pipe(acc,x,pos) end)
    end

    def unpipe(expr) do
      Enum.reverse(unpipe(expr, []))
    end
    
    defp unpipe({:<|>, _, [left, right]}, acc) do
      unpipe(right, unpipe(left, acc))
    end
    
    defp unpipe(other, acc) do
      [{other, 0} | acc]
    end
    
    def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
      {call, line, List.insert_at([], integer, expr)}
    end
    
    def pipe(expr, {op, line, args} = op_args, integer) when is_list(args) do
      {op, line, List.insert_at(args, integer, expr)}
    end
  end
        
      
Copy Code
        
  You can test this operator in iex terminal.
  iex(2)> require Pipe
  iex(3)> import Pipe
  iex(5)> 2 <|> div(1) <|> IO.puts
  2
  :ok
        
      

I hope you understand how pipe operator works . If you want to learn more interesting topics in elixir.

Back To Blogs


contact us