Processes are the basic building blocks of Elixir programming.
In Elixir, all code runs inside processes. Processes are isolated from each other, run concurrent to one another and communicate via message passing. Processes are not only the basis for concurrency in Elixir, but they also provide the means for building distributed and fault-tolerant programs.
Elixir Docs
However, I couldn't grasp the potential of processes until I solved this problem from the Exercism website.
Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. Watch out for concurrent transactions!
Also the problem description literally said create an account that can be accessed from multiple threads/processes (terminology depends on your programming language). π
Primal Instincts
Now I am a web developer. And whenever I hear that something needs to be persisted, my first cry is to use a database.
But hey, this was a simple problem. I am sure they did not want me to open a DB connection.
I did not have too much idea of processes. So I dived into "Elixir in Action" book and noted the below points.
In Elixir, itβs common to create long-running processes that can respond to various messages. Such processes can keep their internal state, which other processes can query or even manipulate.
In this sense, stateful server processes resemble objects. They maintain state and can interact with other processes via messages. But a process is concurrent, so multiple server processes may run in parallel.
Server and Clients
Aha, so the challenge here was to implement the "Server and Clients" pattern using threads and processes. And not the MVC pattern. π
These were my notes from Evernote when I was finally able to crack the puzzle πβΊοΈ
Account is a process with some state(balance)
And then there are other processes which interact with this process.
The rest was easy.
Passing messages
Elixir uses the mailbox
pattern to communicate between two processes.
Just like you send and receive an email in your inbox, every process can send a message to an inbox(different process or even it's own) using the send
function.
Once the message is in the inbox, it waits for its owner to choose to receive it using the receive
function.
As you can see below, the client sends a message to account server process. And the account server process receives a message and handles it.
# Client
def find_balance(account) do
send account, { :balance }
end
# Server
def account(balance) do
receive do
{ :balance } ->
IO.puts("Balance - #{balance}")
account(balance)
end
end
Hold state
Here endless tail recursion
is used to persist the value of balance
for the lifetime of a process.
As you can see when the server receives a message add
, it changes the state accordingly.
# Server
defmodule Procs do
def account(balance) do
receive do
{ :balance, caller } ->
send(caller, {:balance, balance})
account(balance)
{ :add, amount } ->
account(balance+amount)
msg ->
IO.puts "Message not recognized - #{msg}"
account(balance)
end
end
end