in code elixir ~ read.

[Elixir] From Functions |> Processes

Lets start with an anonymous function. Functions are first class citizens in Elixir. They can be bound to a variable and passed around.

$ iex
> func = fn(msg) -> IO.puts "called with message #{msg}" end
> func.("hai")
called with message hai  
:ok

Why not name the function?
In Elixir all named function must be inside a Module. A module is an organizational unit in Elixir. Open up a file named_function.exs and start typing the following in:

# named_function.exs
defmodule NamedFunction do  
  def call(msg) do
    IO.puts "called with message #{msg}"
  end
end  

Lets load this into iex and run it.

> c("named_function.exs")
> NamedFunction.call("hai")
called with message hai  
:ok

Awesome! The iex process called the function call which resides in the module NamedFunction. Lets introduce concurrency here. Due to Elixir's friendship with Erlang, we have Erlang's actor model based concurrency in Elixir. You can spwan a Process and let the process do all the work.

Remember these processes are Erlang processes, managed by the BEAM VM. They are lightweight and usually millions of processes can be spawned on a humble Macbook Pro.

Lets run our named function in a process

> pid = spawn(NamedFunction, :call, ["hai"])
called with message hai  
#PID<0.76.0>

We just ran the function call in a process with PID #PID<0.76.0>. It also printed the output. The function spawn takes a module name as the first argument, the function name you wish the process to run (which resides in the module) as the second argument and a list of arguments to the function as the third. Since call takes a single argument, we send in a list with one element.

We talk to these processes using messages. Each process is equipped with a mailbox to receive and send messages. Lets make our call function wait for a message rather than executing right away. Notice call does not take any arguments anymore.

# named_function.exs
defmodule NamedFunction do  
  def call do
    receive do
      {_pid, msg} ->
        IO.puts "called with message #{msg}"
    end
  end
end  
> c("named_function.exs")
> pid = spawn(NamedFunction, :call, [])
#PID<0.76.0>
> send pid, {self, "hai"}
called with message hai  
{#PID<0.58.0>, "hai"}

We talk to a process using its PID. The function send take the PID of the process as the first argument and the message payload as the second. In our case its a tuple with the current process PID (self) and "hai".

When the process is spawned, it waits on the receive block until the process gets a message. Then it pattern matches (learn the basics of pattern matching here) the payload and executes the body of the match.

Now lets send a message back to the calling process.

# named_function.exs
defmodule NamedFunction do  
  def call do
    receive do
      {pid, msg} ->
        send pid, {:ok, "called with message #{msg}"}
    end
  end
end  
> c("named_function.exs")
> pid = spawn(NamedFunction, :call, [])
#PID<0.76.0>
> send pid, {self, "hai"}
{#PID<0.58.0>, "hai"}
> receive do
>   {:ok, msg} ->
>     msg
> end
"called with message hai"

As you can see, the process sends a message back to the caller process (iex). So we have to write a receive block to read the message.

We will be writing a lot of receive blocks in iex through the rest of the post. So lets encapsulate it within an anonymous function

> do_receive = fn ->
> receive do
>   {:ok, msg} ->
>     msg
> end
>end

Moving on! Lets try to send a second message to the process!

> send pid, {self, "hello"}
> do_receive.()

Woah! iex hung! Well it did not. Its waiting on the receive block for the message from our process. But why didn't the process respond to our second message? That's easy to explain. Once our process respond to first message, it died.

So how do we make or process respond to more than one message? Recursion to the rescue.

# named_function.exs
defmodule NamedFunction do  
  def call do
    receive do
      {pid, msg} ->
        send pid, {:ok, "called with message #{msg}"}
        call
    end
  end
end  
> c("named_function.exs")
> pid = spawn(NamedFunction, :call, [])
#PID<0.76.0>
> send pid, {self, "hai"}
{#PID<0.58.0>, "hai"}
> do_receive.()
"called with message hai"
> send pid, {self, "hello"}
{#PID<0.58.0>, "hai"}
> do_receive.()
"called with message hello"

Notice we are calling call in the receive block? This will make the process wait on receive block every time the process receives a message. Note: Elixir supports tail call optimization. So a new frame is NOT added to the stack every time call gets executed. And hence it's performant.

Now that we made our process respond to multiple messages, we will make it respond to different types of messages.

# named_function.exs
defmodule NamedFunction do  
  def call do
    receive do
      {:pop, pid, msg} ->
        send pid, {:ok, "POP: called with message #{msg}"}
        call
      {:push, pid, msg} ->
        send pid, {:ok, "PUSH: called with message #{msg}"}
        call
    end
  end
end  
> c("named_function.exs")
> pid = spawn(NamedFunction, :call, [])
#PID<0.76.0>
> send pid, {:pop, self, "hai"}
{:pop, #PID<0.58.0>, "hai"}
> do_receive.()
"POP: called with message hai"
> send pid, {:push, self, "hello"}
> do_receive()
"PUSH: called with message hello"

Great!

All data structures in Elixir are immutable. So how do we maintain state? Processes are the vehicles of state. Lets make our process keep track of the number of messages it receives.

# named_function.exs
defmodule NamedFunction do  
  def call(count \\ 0) do
    receive do
      {:pop, pid, msg} ->
        send pid, {:ok, "POP: Message number [#{count + 1}]: #{msg}"}
        call(count + 1)
      {:push, pid, msg} ->
        send pid, {:ok, "PUSH: Message number [#{count + 1}]: #{msg}"}
        call(count + 1)
    end
  end
end  
> c("named_function.exs")
> pid = spawn(NamedFunction, :call, [])
#PID<0.76.0>
> send pid, {:pop, self, "hai"}
{:pop, #PID<0.58.0>, "hai"}
> do_receive.()
"POP: Message number [1]: hai"
> send pid, {:push, self, "hello"}
> do_receive()
"PUSH: Message number [2]: hello"

When the process is spawned the count will be set to 0 (\\ is used to set default arguments) Every time the process receives a message we increment the count by one when re-calling the call function, thus maintaining state without manipulating any data structure.

Doesn't :pop and :push sound familiar? Yes! I am implementing a stack! Lets rename the module to Stack and make the process maintain a list rather than count.

# named_function.exs
defmodule Stack do  
  def call(list \\ [1,2,3]) do
    receive do
      {:pop, pid, msg} ->
        send pid, {:ok, "POP: called with message #{msg}"}
        call(list)
      {:push, pid, msg} ->
        send pid, {:ok, "PUSH: called with message #{msg}"}
        call(list)
    end
  end
end  
> c("named_function.exs")
> pid = spawn(Stack, :call, [])
#PID<0.76.0>
> send pid, {:pop, self, "hai"}
{:pop, #PID<0.58.0>, "hai"}
> do_receive.()
"POP: called with message hai"
> send pid, {:push, self, "hello"}
> do_receive()
"PUSH: called with message hello"

Our Stack process doesn't do much yet. But now it maintains a list of numbers. Lets implement pop first. We send the message {:pop, process} to the process and the process should return the top most element (1)

# named_function.exs
defmodule Stack do  
  def call(list \\ [1,2,3]) do
    receive do
      {:pop, pid} ->
        [head|tail] = list
        send pid, {:ok, head}
        call(tail)
    end
  end
end  
> c("named_function.exs")
> pid = spawn(Stack, :call, [])
#PID<0.76.0>
> send pid, {:pop, self}
{:pop, #PID<0.58.0>}
> do_receive.()
1  
> send pid, {:pop, self}
{:pop, #PID<0.58.0>}
> do_receive.()
2  

We use pattern matching to extract the head of the list (topmost element) and send back in the reply. The current state of the stack is the tail of the list. So we send the tail in the recursive call.

Lets make the process respond to show message to view the current state of the stack.

# named_function.exs
defmodule Stack do  
  def call(list \\ [1,2,3]) do
    receive do
      {:pop, pid} ->
        [head|tail] = list
        send pid, {:ok, head}
        call(tail)
      {:show, pid} ->
        send pid, {:ok, list}
        call(list)
    end
  end
end  
> c("named_function.exs")
> pid = spawn(Stack, :call, [])
#PID<0.76.0>
> send pid, {:show, self}
{:show, #PID<0.58.0>}
> do_receive.()
[1,2,3]
> send pid, {:pop, self}
{:pop, #PID<0.58.0>}
> do_receive.()
1  
> send pid, {:show, self}
{:show, #PID<0.58.0>}
> do_receive.()
[2,3]

We can clearly see our :pop message handling works. Lets push a number on to the stack.

# named_function.exs
defmodule Stack do  
  def call(list \\ [1,2,3]) do
    receive do
      {:pop, pid} ->
        [head|tail] = list
        send pid, {:ok, head}
        call(tail)
      {:show, pid} ->
        send pid, {:ok, list}
        call(list)
      {:push, pid, item} ->
        send pid, {:ok, :pushed}
        call([item | list])
    end
  end
end  
> c("named_function.exs")
> pid = spawn(Stack, :call, [])
#PID<0.76.0>
> send pid, {:show, self}
{:show, #PID<0.58.0>}
> do_receive.()
[1,2,3]
> send pid, {:push, self, 10}
{:push, #PID<0.58.0>, 1}
> do_receive.()
:pushed
> send pid, {:show, self}
{:show, #PID<0.58.0>}
> do_receive.()
[10,1,2,3]

As a part of the :push message we will also send the item we want to push on to the stack. We again use list pattern matching to append the item to the list.

Yay! We have a basic stack process running! We can improve the process to handle cases like popping an empty stack and so on. But that is for some other time.

So to recap we started with an anonymous function and ended up with a decent implementation of a stack process. If you look at our process closely, it is nothing but a small server responding to messages/requests. In Elixir, your programs will end being a set of such servers sending messages to each other. Hence Elixir(Erlang) offers easier ways to create such servers; the most basic of which is a GenServer. In the next post we will rewrite our Stack using a GenServer.

comments powered by Disqus