Elixir another language for BEAM/Erlang Virtual machine

Madanasekaran.P

Introduction

Jose Valim the creator of Elixir was a Rails committer. Seeing a version of Rails2.2, which was called “thread-safe Rails”, was not actually thread-safe, he began looking for concurrency models in functional languages like Erlang and Clojure. He was impressed with the Concurrency model and performance of Erlang VM but wanted to use (in his opinion) a more approachable Ruby-syntax. In his new language, he added meta-programming abilities of Clojure Macros and Polymorphism of Clojure Protocols. “ Elixir offers developers the functional power and concurrent resilience of Erlang, with friendlier syntax, libraries, and meta-programming. Elixir compiles to Erlang byte code, and you can mix and match it with Erlang and Erlang tools. Despite a shared foundation, however, Elixir feels different, perhaps more similar to Ruby than to Erlang”. The above quote sums up Elixir. Though much of the Erlang community is of the opinion that a new language need not be learnt for the sake of syntax, there is some enthusiasm for its Clojure-like Macros which enables creation of libraries extending the language to new domains and Productivity tool like Mix, which is part of the distribution rather than a separate download like rebar, the build tool for Erlang. It is interesting to note the initial version of Mix was delivered by the same people who coded Lein the build and dependency management tool for Clojure. Joe Amstrong, the chief creator of Erlang, hopes that Elixir will lower the entry point for programmers in other languages to Erlang world. The Ecosystem(libraries available)  of JavaScript or Ruby is so huge as to deter a programmer in those languages from veering round to Elixir, unless the number of requests for their applications are very huge and they want to avail themselves of the scalability offered by Erlang VM.

  • Concurrency and fault-tolerance of Erlang VM
  • Lazy evaluation from Haskell.
  • Protocols, macros and pipelines from Clojure.

This combination brought in by Elixir is worth the try, as can be seen from the number of books on Elixir and its different use cases. The language is very young but is used in Production in different domains -from web development to game platforms, to embedded devices.  Erlang is powering around 50% of telecom networks. The success story of its concurrency model is WhatsApp. WhatsApp, which was acquired for billions of dollars, was running millions of processes on a single server, supported 450 million users, and had only 32 engineers. But Erlang does not seem to be used much in Web applications, though it has good web servers like Cowboy. Elixir tries to make use of the Cowboy other Erlang libraries and VM and build libraries of its own using Macros and breach into the territory of web-applications.

Installation

If you have Erlang/OTP-18 in your machine you can install Elixir version 1.2.0 onwards. For windows there is an installer and you can down load and install Elixir making use of it. After installation, go to installation folder/bin in the command prompt and execute

  iex
.The interactive shell will appear displaying
Eshell version 7.2.1 (abort with ^G)
Interactive Elixir (1.2.3) -Press Ctrl + C to exit. ( type h() and ENTER for help)
iex(1)
The version numbers may vary according to your installation.

Sequential Programming:
execute 1 + 2.You will see  3
 iex(2) to enable you to input the next command.
Execute “Hello,Ganesh”  
Output “Hello, Ganesh”
If you execute 
IO.puts(“Hello, Ganesh”)
the output will be
        Hello, Ganesh
:ok

Notice that "Hello, Ganesh!" is written to the screen without the quotes. Further, what is this :ok ?
IO.puts is a function with side-effects; it writes its parameter's value to the screen. Since Elixir statements are all expressions, every statement must return a value. The value returned in this example “ :ok” is an atom which signifies to the caller,  that the operation has been successful. If there is any error it will return the atom “:error”.

Elixir more or less it has all the constructs we saw earlier in Erlang with slightly modified syntax. For example elixir does not require a “full stop” or dot operator and the above “1 + 2” in Erlang shell would have been “1 + 2.”There are some differences in naming atoms and variables.

Atom. In Erlang atom “hi”(starts with small letter) Elixir “:hi”( extra colon)

Variable. Erlang "Hi" (starts with capital letter) . Elixir-hi. For a full list of such changes see Erlang/Elixir Syntax: A Crash Course available on Elixir language web site. Some of the other important changes are Elixir permits reassignment of a variable

In Erlang

X = 2.

X = X + 1 % matching error as "=" is actually a matching operator.

In Elixir the above code will work and iex will return 3. The matching operator in Elixir is "^ =". If you execute

^x = x +1

You will see a match error.

Another important change is string is list of characters in Erlang. In Elixir it is a separate module with its own methods. Execute

String.length("Ganesa") and see 6 as output.

String.reverse("Muruga")

Output aguruM

String.capitalize("muruga")

Output: MURUGA

Concurrent Programming: using shell as a process and sending it messages

iex(1) self()
#PID<0.57.0>
send(self(), :ganesha) #shell process sends a messageto itself
:ganesha
iex(2)
send(pid, :vekatesa) # shell process sends a message to its pid
:vekatesa
iex(3) flush() % flushes the mailbox of shell
:ganesha
:vekatesa
:ok  # return value of flush others are side effects           

Shell process creates child process


pid2 = spawn fn 6 * 4 end #process spawned from an anonymous function

#PID<0.68.0> 

Process.alive?(pid2)

false #process memory reclaimed once the function is executed- short-lived and transient process

iex(3) parent =self() # shell's pid stored in parent

#PID<0.57.0>

iex(4) spawn fn -> send(parent, 2* 3 ) end # spawns a child process that sends a message to shell

#PID<0.60.0>

iex(5) receive do #receiving the message in the shell

iex(5) x -> IO.puts("#{inspect x"}) #Checking the queue in the mailbox; see iex(5) does not change

iex(5) end % end receive

6 # message in the mailbox put to the console-side effect

:0k # return value

# spawn a process from an anonymous function that receives and processes a message

iex(6)  rec = spawn fn -> receive do {pid,msg} ->IO.puts msg end end

send rec, {self, “Rama”} # sending a message from the shell to the “rec” child process


Recall in Erlang you have to necessarily define a function in a module and compile the file before spawning a process. In Elixir you can do the same; but some more also. We saw the shell sending a message to it-self or creating a child process to send a message to it and receive the message and inspect the mailbox etc . But doing these things in the shell, though possible, is somewhat tedious. Elixir gives you the option of creating a module in a script file (.exs) and importing the same in the shell and using it. Another option is, as in Erlang, creating an .ex file and compiling it and using it. We will see some compiled samples.

Compiled Samples

Though IntelliJIdea has a Elixir plug-in, it does not offer support for creating Elixir project. Elixir distribution comes with a build tool called “mix”. Since our code examples will be small we will not be using the same and we can use an editor and compile it with “iex”. We will create all our elixir files in a separate folder-in my case E:\ElixirApps.Save the following file

echo.ex

defmodule Echo do
def report do
receive do
msg -> IO.puts("Received #{msg}")
report()
end
end
end

In the shell
iex(1) c(“echo.ex”) % compile file -name within “ “.
Echo % module name
iex(2)  pid =spawn(Echo, report,[])
send(pid, :ganesa) % send function takes two arguments 1) a pid 2) an atom that will be matched to variable “msg” in receive
:ganesa
Received ganesa %report processes the ‘msg’ in its mailbox and puts it to the console.
iex(3)

You can see we did not create any main() or such entry point, as the Erlang VM does not require us to create it. A simple spawn() will suffice.
The spawn/3 function, turns our custom function “report/0into a free-standing process. The arguments to spawn are the module name, the function name (as an atom), and a list of arguments for the function. Even if you don’t have any arguments, you need to include an empty list in square brackets. The spawn/3 function will return the pid of the spawned process, which is captured in a variable, here pid.

Another sample: One may ask whether we should necessarily go to the shell to spawn a process, as shown in the above sample.
As we will see in the next sample, we can spawn the process in the file itself but outside the module definition.

hello1.ex

defmodule Hello1 do
def greet do
receive do 
{sender, msg} -> # only messages with the pid of sender and matches this pattern will be processed
send sender, { :ok, "Hello, #{msg}" } #msg received is sent back with an :ok atom to the sender
end
end
end
pid = spawn(Hello1, :greet, [])  # greet process spawned
send pid, {self, "Ganesh!"}  # shell process sends the message to greet process  ---1)
receive do  # shell process handles the message received in receive
{:ok, message} ->
IO.puts message # shell process puts it to the console
End

iex(1) c(“hello1.ex”)

Hello, Ganesh! # message
[Hello1] # module name
iex(2)

  • Sending message to the child process is just making a function call. The pid of the self and message are passed as parameter. The first element in the tuple  should match with the first element in the  tuple in the “receive” clause of the greet process. Otherwise the message sent to and queued in the mailbox of greet process will not be processed and if the mail-box swells with such unprocessed messages the performance of the system will be affected.

Another sample to receive multiple messages:

In the above sample the greet process will die after processing the message and sending it back , as once the function is executed it has nothing left to do. Normally as the processes in Erlang or Elixir are spawned from some function, they die as soon as the execution of the function is over and the memory allocated to them is reclaimed. But this default behavior can be changed and a process can live by making a recursive call in the receive clause. Then the process will wait for the next message. If the time-slice allotted to it is over it will be suspended but once another message is received, it will join the scheduler’s queue and process the message once the scheduler allots it a time –slice. This process will go on till the program is closed.

hello2.ex

defmodule Hello2 do
def greet do
receive do
{sender, msg} ->
send sender, { :ok, "Hello, #{msg}" }
greet # recursive call
end
end
end
# here's a client
pid = spawn(Hello2, :greet, [])
send pid, {self, "Rama!"}
receive do
{:ok, message} ->
IO.puts message
end
send pid, {self, "Venkatesa!"} # second message
receive do
{:ok, message} ->
IO.puts message
End

iex(1) c(“hello2.ex”)
Hello,Rama!
Hello, Venkatesa!
[Hello2]
iex(2)

Links and Monitors

The following observations made about links and monitors in an earlier Erlang article equally applies to Elixir. If a linked process terminates, it sends an exit message to another linked process before exiting. The linked process which received the EXIT message sends the Exit message with the reason received with its Pid to another linked process and then exits. The propagation goes on till all the processes in the link set are terminated. This default behavior is useful when a group of processes carries out a single “transaction”. For other cases the behavior can be changed and “termination” can be restricted. One way of doing this is to make process in the link set, a system/supervisor process. System/supervisor processes are basically normal processes which can trap exit signals by setting the process flag trap_exit. When the function process_flag(trap_exit, true)  is called, exit signals are converted into to regular messages of the format {'EXIT', Pid, Reason} and queued in the mail-box. Once an EXIT signal is trapped in a system/supervisor process, there is no further propagation and only some process in the set that received the EXIT signal before it was trapped by system/supervisor process will die. The system/supervisor process can take an appropriate action like restarting the processes that got terminated abnormally.  Another way of stopping the propagation is by using monitor in place of link. Link is bi-directional, that is, both the linked process can observe one another. Monitor is unidirectional. If A monitors B and B dies, then A will be sent a DOWN message but not the other way around and there is no further escalation. In links the death of either process would result in the other process being informed and all the processes in the link set will terminate,  unless you trap the error message and handle it in a supervisor process.

As in Erlang, when you use links and monitors you have to design carefully to avoid race-conditions and deadlock. Hence it is better to use OTP behaviors and supervisor in place of process and links. In the words Joe Amstrong a creator of Erlang/OTP Erlang “The power of OTP comes from the fact that properties such as fault tolerance, scalability, dynamic-code upgrade, and so on, can be provided by the behavior itself”. You need not bother about race conditions and deadlock. Above all it enables the programmer to write sequential code without bothering about the underlying concurrency constructs like spawn(),send and receive.Dave Thomas the author of Programming Elixir says “Elixir uses the OTP framework for constructing process trees, and OTP includes the concept of process supervision. An incredible amount of effort has been spent getting this right, so I recommend using it most of the time”.

Sample for link

hello_actors.ex

defmodule Actor do
def loop do
receive do
{:greet, name} -> IO.puts("Hello #{name}")
{:praise, name} -> IO.puts("#{name}, you're great")
{:shutdown} -> exit(:normal)
end
loop
end
end

Process.flag(:trap_exit, true)  % ---------1)
pid = spawn_link(Actor,:loop, [])   %--------2)

send(pid, {:greet, "Ganesha"})
send(pid, {:praise, "Veketesa"})
send(pid, {:shutdown})                            %-----3)

receive do
{:EXIT, ^pid, reason} -> IO.puts("Actor
has exited (#{reason})")
end

Output
Hello Ganesha
Venketesa you’re great.
Actor has exited (normal)

  • As in Erlang Process flag “trap_exit” is used to catch the dying declaration from a process.
  •  Spawn_link is used to spawn and link as an atomic step, as in rlang. ie The spawn_link call spawns a process and links it to the caller in one operation.
  • The child process is asked to shutdown.

Conclusion:

The syntax of Elixir is friendly to the programmers from “C” group of languages and more acceptable to them than Erlang.  The aim of Jose Valim, its creator, was to create a web framework that is productive like Rails but runs on Concurrent and fault-tolerant Erlang Platform. As Elixir is palatable to programmers from Java or Ruby world, they can use Elixir, if they want the concurrency, scalability and fault-tolerance of Erlang platform. It has some good libraries also. To mention a few, Phoenix web framework, Ecto a DSL for database queries. Ecto is like  LINQ and it exemplifies the power of macros to create DSLs for specific domains. Phoenix is by Chris McCord, another programmer from Rails shop.  The focus of Elixir appears to be web applications from the start. Dynamo is an experimental web framework that runs on Elixir. Jose Valim created it with the main goal of testing ideas and many of its APIs were extracted and improved into separated projects like Plug. Web frameworks like Phoenix and Sugar have been built on top of Plug. Valim has become a committer to Phoenix.

To sum up Elixir programming language has 1) functional programming with immutable state 2) an actor-based approach to concurrency 3) a tidy, modern syntax. 4) above-all, it runs on the industrial-strength, high-performance, distributed Erlang VM. If you use Erlang or Elixir, You can stop worrying about a) protecting your data consistency in a multithreaded environment and scaling your applications.The VM will take care of them. Jose Valim stresses the extensibility brought by Macros and has actively associated himself with the web libraries and frameworks created with Elixir. Another interesting fact is that Bruce Tate, the author of seven languages in Seven weeks is using Elixir in his Company “icanmakeitbetter”, a research firm.

The following observation about Erlang of Eric Stanmen from Klarna, a company providing web-services using Erlang equally applies to Elixir:  “the most important aspects is that it makes you to think of a problem in a new way. In a concurrent way”.








}