Before we get started, I strongly encourage to have a REPL/IEx to try out the examples. More details about installation can be found here.
What is Elixir ?
Wikipedia describes “Elixir as a functional, concurrent, general-purpose programming language that runs on the Erlang virtual machine (BEAM)”
Why Elixir ?
- Easy to learn
- Builds on BEAM VM which is very mature
- Very good support for building low latency, distributed, fault tolerant applications
- Offers very good debugging tools
- Dirt cheap concurrency
- Metaprogramming
- Functional (is the new cool :D)
Helpful Commands To Get Started
IEx offers a lot of helper functions. It is very similar to IPython. Lets get started.
iex(1)> h # <- Interactive Elixir help
iex(2)> i # <- prints information about last termelixirSimilar To Python
Basic Types
All data structures in Elixir are immutable unlike in Python. Each operation creates a new copy of the data structure. Internally these data structures are optimized to be efficient by minimizing making copies and reusing parts of changed data structures.
Atom
Elixir got a type called atom, whose name and value are the same. It starts with : or an uppercase letter
iex> is_atom(:hello)
true
iex> is_atom(Hello)
trueelixirAtoms are used as tags for data. We will see it when using Map(Dict in Python) in Elixir.
List
List in Elixir is similar to list in Python.
iex> a=[1,2,3,4]
iex> List.replace_at(a, 1, 44)
[1, 44, 3, 4]elixirList also offers elegant destructuring abilities.
iex> [b|c] = a
[1, 2, 3, 4]
iex> b
1
iex> c
[2, 3, 4]elixirIt follows the format [head|tail]. The same functionality in Python would be
In [1]: a = [1,2,3,4]
In [2]: [b, *c] = a
In [3]: b
Out[3]: 1
In [4]: c
Out[4]: [2, 3, 4]pythonRange
Ranges in Elixir are represented as 1..3, same as range(1,3) in Python.
iex> i 1..3 # <- Get information about 1..3
Term
1..3
Data type
RangeelixirTuple
Tuples in Elixir are similar to those in Python and can store any data and are ordered.
iex>{:tu, "hello", 5}
iex> elem(a,1) # <- Accessing Element
"hello"elixirSame in Python would be as follows.
('tu', "hello", 5)pythonBitString and Binary
Elixir has BitString which are data represented in bits and binary where data are stored as bytes. This is useful for pattern matching. bitstring allows to manipulate and match on bits, while binary works on bytes. <<>> is used to define a binary. A binary is a bitstring when the number of bits is divisible by 8(i.e a byte).
# A bitstring
bitstr = <<<4 :: size(3) >> # 3 bits 100, 3 not divisible by 8
is_binary(bitstr) # false
is_bitstring(bitstr) # trueelixirNotice the size(3) which means 3 bits.
# A binary
binary = << 4 >> # 8 bits or 1 byte, so it becomes a binary
is_binary(binary) # true
is_bitstring(binary) # trueelixirStrings
Strings in Elixir are represented internally as UTF-8 encoded binaries, which are a sequence of bytes.
iex> is_binary("hellö")
true
# Bytesize is 6, even though there are only 5 letters, as unicode of ö needs 2 bytes
iex> byte_size("hellö")
6
# To get length of String use String.length
iex> String.length("hellö")
5elixirString module in Elixir offers functions for working with strings. Strings can be sliced using String.slice.
iex(1)> name="Übung"
iex(2)> String.slice(name, 0..-1)
"Übung"
iex(3)> String.slice(name, 0..3)
"Übun"elixirUnlike in Python ‘ and “ are not same in Elixir. Double quotes(“) are used for Strings and single quotes ‘ for Char lists. Char list is a List with characters, while String is a UTF-8 encoded binary. To see the internal representation of a string we add null byte <<0>>.
# String
iex(1)> s = "hellö"
"hellö"
# Add null byte to see representation
iex(2)> s <> <<0>>
<<104, 101, 108, 108, 195, 182, 0>>
# Charlist
iex(3)> c = 'hellö'
[104, 101, 108, 108, 246]elixirKeyword List
Keyword lists are list of tuples(which can have anything) with atoms as keys. The keys are ordered and hence are used as parameter lists.
iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]elixirThe Elixir syntax for these is [key: value]. For a similar functionality in Python, we can use list of tuples.
Map
Map is the equivalent of dict in Python. It follows the syntax %{“key” => “value”}
iex> map = %{"M" => "QA", "N" => 100, 200 => "D"} # <- A Map/dictionary
%{200 => "D", "M" => "QA", "N" => 100}
iex> map["M"] # <- Access value of Map
"QA"
iex> %{map | "M" => "DE"} # <- Update Value
%{200 => "D", "M" => "DE", "N" => 100}elixirYou can also use Map module for working with maps. Maps can be nested inside maps, whereas dict in python can’t do it, as dict is unhashable. Python can use named tuples from collections, to get similar results.
# In Elixir
iex> %{%{"M" => "DE"} => "FE"}
%{%{"M" => "DE"} => "FE"}elixir# In Python
In [1]: {{"M":"DE"}: "FE"}
TypeError: unhashable type: 'dict'pythonIn Elixir nested map updation and access can be done using Kernel.put_in and Kernel.get_in
iex> nested = %{"name" => %{"first" => "A"}}
%{"name" => %{"first" => "A"}}
# Update nested using put_in
iex> put_in(nested["name"]["first"], "B")
%{"name" => %{"first" => "B"}}elixirBoolean
In Python many values can be a false along with False. For eg: [], {}, None, 0, 0.0 etc return False.
In [1]: if 0:
...: print("Not False")
...: else:
...: print("False")pythonIn Elixir it is a bit different. The only false values in Elixir are false and nil. They are actually :false and :nil. Others like [] or 0 are not a false value.
iex> if 0, do: IO.puts("Not false")
Not falseelixirAs you would have already inferred ‘#’ starts a comment in both Python and Elixir.
Modules and Functions
Modules are defined in Elixir using defmodule. Modules in Python are file based. In Elixir you can have multiple modules in a single file. Functions in Elixir should be defined inside a module. Like Python functions, Elixir also supports default arguments with ‘\\’ syntax. Functions follow the syntax def function-name do function-body end.
If the function-body is one line, it can be written as def function-name, do: function-body
defmodule Factorial do # <- Defines a module, with defmodule
# Functions are defined with def
def factorial(0), do: 1 # <- Pattern matching in arguments
def factorial(n), do: n * factorial(n - 1) # <- Recursion
def mul_by(x, n \\ 2) do # <- Default argument for n is 2
x * n
end
endelixiriex> Factorial.factorial(5)
120elixirModule can also have a struct inside it. Structs are similar to Maps, but offers compile time checks and allows providing default values. It can be considered as map with a fixed structure/attributes. Structs can be defined using defstruct keyword. For example we can define a struct Product which holds a name.
defmodule Product do
defstruct [:name]
end
# And a new structure can be constructed using below syntax
%Product{name: "Hello"}elixirAnonymous Functions / Lambdas
lambda equivalent of Elixir is fn. It follows the syntax, fn (x) -> body end.
One main difference with fn and def is that fn makes a closure, which def doesn’t, as shown in example below.
iex> company = "T"
"T"
# Anonymous functions are defined with fn and forms a closure
iex> sayName = fn p -> IO.puts("I am #{p} in #{company}") end # <- Anonymous functionelixiriex> sayName.("M") # <- Note '.' after sayName
I am M in T # <- 'T' came from closureelixirAlso anonymous functions in Elixir doesn’t suffer from late-binding problems like in Python. Also since Elixir data is immutable, closure works like it got a copy of the value and not an identifier bound to lexical scope like in Python.
Variable rebinding
Variable rebinding is a source of confusion for Elixir beginners. Consider the following code.
iex(1)> name = 'J'
'J'
iex(2)> name = 'K'
'K'
bash
How is that even possible?. Isn’t name immutable?. Yes it is immutable. What happens here is name got rebind to a new value. So anyone holding a reference to name, when name was ‘J’, will not see any difference. Only new users of name with value ‘K’ gets the new value. See the below code which shows it in action.
iex(1)> company = "T"
"T"
iex(2)> sayName = fn p -> IO.puts("I am #{p} in #{company}") end # <- Anonymous function
#Function<6.99386804/1 in :erl_eval.expr/5>
iex(3)> sayName.("M")
I am M in T
:ok
iex(4)> company = "J"
"J"
iex(5)> sayName.("M")
I am M in T # <- See company is still T
:okelixirNot Similar To Python
Pattern Matching
= is called match operator. Different from usual programming languages like Python. JS/Python3 also offers a limited pattern matching. It allows only either assignment or matching and not both. (https://gist.github.com/mikaelbr/9900818). Elixir offers both at the same time, as assignment operator(=) is actually a match operator.
iex(60)> {:ok, r} = {:ok, 42}
{:ok, 42}
iex(61)> r
42
# Match fails, as :fail != :ok
iex(63)> {:fail, r} = {:ok, 42}
** (MatchError) no match of right hand side value: {:ok, 42}
# Pattern matching on lists
iex(63)> [h|t] = [4, 2, 1]
[4, 2, 1]
iex(63)> h
4
iex(64)> t
[2, 1]
# Pin operator, to pin your values
iex(65)> name = "name"
"name"
iex(66)> {^name, h} = {"name", 8}
{"name", 8}
iex(67)> h
8
iex(68)> {^name, h} = {"hello", 8}
** (MatchError) no match of right hand side value: {"hello", 8}
# The same can be done for functions too.
defmodule Fact do
# Matches when input is 0, and precedence of matching is from top to bottom.
def fact(0), do: 1
def fact(n), do: n*fact(n-1)
endelixirCase statement can be used to match on arguments. Python unfortunately, doesn’t offer such an elegant pattern matching support.
iex(6)> v = {1,2}
{1, 2}
iex(7)> case v do
...(7)> {1,3} -> 5
...(7)> _ -> 4 # it matches everything
...(7)> end
4elixirThats all for now. See you in part 2