Elixir
JUST ANOTHER SYNTAX FOR ERLANG?
What is Erlang?
- OTP
- Functional
- Distributed
- Fault-tolerant
- Hot-code loading
Old Programming language (1986), built by Ericsson with the aim to improve the development of telephony applications.
Major characteristics:
What is Elixir?
- All Erlang/OTP characteristics (same VM)
- Meta-programmation
- Polymorphism
New Programming language (2012), built by José Valim with the aim to improve some of the Erlang flaws.
Major characteristics:
Erlang
Elixir
-module(bank_account).
-behaviour(gen_server).
-export([start_link/0, init/1, handle_call/3]).
-export([deposit/2, state/1, withdraw/2]).
start_link() ->
gen_server:start_link(?MODULE, [], []).
init(_Arguments) ->
{ok, 0}.
deposit(AccountPid, Amount) ->
gen_server:call(AccountPid, {deposit, Amount}).
state(AccountPid) ->
gen_server:call(AccountPid, state).
withdraw(AccountPid, Amount) ->
gen_server:call(AccountPid, {withdraw, Amount}).
handle_call({deposit, Amount}, _From, AccountState)
when Amount < 1 ->
{reply, {error, "Deposit only accept positive amount."}, AccountState};
handle_call({deposit, Amount}, _From, AccountState) ->
NewAccountState = AccountState + Amount,
{reply, {ok, NewAccountState}, NewAccountState};
handle_call(state, _From, AccountState) ->
{reply, AccountState, AccountState};
handle_call({withdraw, Amount}, _From, AccountState)
when Amount < 1 ->
{reply, {error, "Withdraw only accept positive amount."}, AccountState};
handle_call({withdraw, Amount}, _From, AccountState)
when Amount > AccountState ->
{reply, {error, "Not enough money on the account."}, AccountState};
handle_call({withdraw, Amount}, _From, AccountState) ->
NewAccountState = AccountState - Amount,
{reply, {ok, NewAccountState}, NewAccountState}.
defmodule BankAccount do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, 0)
end
def deposit(account_pid, amount) do
GenServer.call(account_pid, {:deposit, amount})
end
def state(account_pid) do
GenServer.call(account_pid, :state)
end
def withdraw(account_pid, amount) do
GenServer.call(account_pid, {:withdraw, amount})
end
def handle_call({:deposit, amount}, _from, account_state)
when amount < 1 do
{:reply, {:error, "Deposit only accept positive amount."}, account_state}
end
def handle_call({:deposit, amount}, _from, account_state) do
new_account_state = account_state + amount
{:reply, {:ok, new_account_state}, new_account_state}
end
def handle_call(:state, _from, account_state) do
{:reply, account_state, account_state}
end
def handle_call({:withdraw, amount}, _from, account_state)
when amount < 1 do
{:reply, {:error, "Withdraw only accept positive amount."}, account_state}
end
def handle_call({:withdraw, amount}, _from, account_state)
when amount > account_state do
{:reply, {:error, "Not enough money on the account."}, account_state}
end
def handle_call({:withdraw, amount}, _from, account_state) do
new_account_state = account_state - amount
{:reply, {:ok, new_account_state}, new_account_state}
end
end
Syntax DiffErence
- Erlang modules are nothing more than symbols
- Elixir can easily call erlang code by using symbols
- :bank_account.start_link
- Elixir modules are nothing more than symbols prefixed by Elixir
- :"Elixir.BankAccount" == BankAccount
- Erlang can call Elixir code by using symbols
- 'Elixir.BankAccount':start_link()
Interoperability
Elixir additions
1. Encoding
Strings and UTF-8 encoding are the default whereas Erlang default is charlists
"hello" #=> ascii in Erlang
"hello" #=> UTF-8 in Elixir
Elixir additions
2. Tasks
task = Task.async(fn -> do_some_hard_work() end)
# Do some stuff here
result = Task.await(task)
Layer on top of GenServer, intended to execute a specific action asynchronously without storing state
Elixir additions
3. Agents
{:ok, pid} = Agent.start_link(fn -> %{} end)
Agent.update(pid, fn state -> Map.put_new(state, :my_key, 42) end)
Agent.get(pid, fn state -> Map.get(state, :my_key) end)
#=> 42
Layer on top of GenServer, intended to store a mutable state without defining any specific action
Elixir additions
4. Protocol
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
A bit like an Interface. You define the list of function which needs to be implemented by other modules
Elixir additions
5. Lisp-ish macro
defmodule MyLogic do
defmacro unless(expr, opts) do
quote do
if !unquote(expr), unquote(opts)
end
end
end
require MyLogic
MyLogic.unless false do
IO.puts "It works"
end
Erlang has a preprocessor style macro whereas Elixir has a more advanced macro style: AST manipulation,...
Elixir additions
6. Doctests
defmodule MyApp.Hello do
@moduledoc """
This is the Hello module.
"""
@doc """
Says hello to the given `name`.
## Examples
iex> MyApp.Hello.world(:john)
:ok
"""
def world(name) do
IO.puts "hello #{name}"
end
end
Documentation is a first class citizen. To be sure code in documentation is not outdated, it's possible to make it part of the test suite.
Elixir future additions
7. GenBroker + GenStage
Current GenEvent implementation can be a problem because:
- event handlers are synchronous
- event handlers live in the same process than the server so
- hard to supervise
- tricky to use in a fault tolerant way
Elixir Tooling
1. Mix
A task runner. It is used to start servers, install dependencies, run tests,...
2. Hex
Package manager allowing to download Erlang and Elixir packages
3. Guides and books
Good starter guide, tutorials and books explaining how the language work, how to use it.
Conclusion
The syntax is the most obvious difference between Erlang et Elixir but not the only difference.
Both languages shares a common ground: same VM, same OTP library, ... but Elixir also aims to improve some of the Erlang flaws.
I wish this talk helped you understand Elixir enthusiasts are not all foolish rubyists following blindly one of their old leader into a new language :)
Elixir is really awesome. Thanks to OTP.
Elixir
By Kevin Disneur
Elixir
Just another syntax for Erlang?
- 1,434