Elixir In Action By SAŠA JURIC ́ summary (1–3)

Redmen Ishab
9 min readJun 2, 2022
elixir in action book

A comprehensive book on Elixir ,With multiple other learning resources I embraced this book in the heart of my learning journey of elixir. An effort to giving back to the community I have tried my best to highlight the key points to summarize each and every chapter of this amazing book.

Chapter 1:

Elixir was developed by José Valim in the effort of making a syntactical better language with improvement in the productivity and extensibility of the Erlang language which was developed for telecommunication system proven for it’s fault tolerant, high availability and scalability. Elixir inherits all these amazing features of it’s parent high availability, concurrency, server-side systems and full-blown development platform.

Being functional language every task is performed with the help of functions and immutable datas.

Since every good things have some counter sides too and downside of Elixir are :

  1. The speed is compromised due to being dynamically typed language.The data type and it’s size has to be determined during the run time and therefore fully optimized byte code couldn’t be generated.
  2. The ecosystem is relatively small in comparison to popular languages like JS, Java , Python etc. To this date of writing It has approx. 20k star and around 3k forks in elixir-github repo.

Chapter 2:

Variables:

The variables are dynamically determined during the run time of the program on the basis of types of data assigned to it. The variable value is immutable and thus on updating the variable the memory location the variable is pointing is updated however the value/data is immutable. Such unused memory location is cleared automatically at the end by Elixir garbage collector.

test_variable = 123
test_variable = "123"
test_variable = :abc

Function:

In general the function is always written inside a module. The reason behind is Elixir being functional language has large number of functions thus to ensure the organization of code and increase in maintenance and readability. It’s type are public and private.

Syntax:

defmodule ModuleName do#public function declaration syntax 
def
function_name( arguments1, arguments2 ) do
#block of statement
end
#private function declaration syntax : It can only be invoked by other function in the same module
defp function_name (arguments1, arguments2) do
#block of statement
end
end

Note : The parenthesis is optional for function with no arguments and the last statement is the return type of the function by default.

The above function is represented be Elixir compiler as function_name/2 -> Here the 2 indicates the number of arguments that the functions take and called as “arity”.

Anonymous function / Lambda :

The function assigned to a variable which is bound during the run-time of the program. “fn” keyword is used to declare such function in elixir.

#syntax for lambda function
variable = fn () ->
#block_of_statement
end
#Example
area = fn (r) ->
3.14 * r * r
end
area.(5) #-> calling an lambda function with "." operator

Module attributes:

It is used for making compile time constants or an attribute which can be queried in runtime.It is declared using ‘@’ sign and called like wise.

defmodule Area do@moduledoc """This is a module to calculate area""" #this acts as a attribute that is queried in runtime@pi 3.14 #this acts as a run time constant def circle(r) do
@pi *r * r #using the symboling constant to calculate area
end
end

Note : By now you might have an idea “#” is used for writing the comments in Elixir.

Data types :

  1. Number : int, float , exponential numbers. The memory size is allocated dynamically for integer and 32 or 64 bits for float .
  2. Atom : it is similar to a constant. It starts with “:” sign and the name as “:pi”. It has a separate atom table thus enabling higher performance and memory optimization. It’s widely adopted.
    p.s: There is boolean type in elixir true, false are also atom as “:true” and “:false” respectively.“:nil” is equivalent for null . However we can write them without the colon sign. i.e true, false, nil .
  3. Binaries and bit strings: A binary is a chunk of bytes. You can create binaries by enclosing the byte sequence between << and >> operators.<> is used to concat the binary strings. Eg:
    <<1, 2 >> <> < 3, 4 >
    <<1,2,3,4>>
  4. Strings: Elixir doesn’t have a dedicated string type. Instead, strings are represented by using either a binary or a list type. Strings can be written inside double-quote syntax and concatenated using ‘<>’ just like binary strings. Ex:
    “Hi” <> “ there”
    “Hi there”
  5. Tuples: Untyped structure or records to group fix number of elements together inside a { } parenthesis separating with ,(comma) operator.It can contain any of the above data types. Eg:
    { :atom1, 123, “my_name” }
  6. Lists : Lists in Erlang are used to manage dynamic, variable-sized collections of data.It resembles an array in other languages as enclosed in [ ] brackets, However the list is a linked list with head | tail in every item of the list. Eg with some basic operations:
    prime_numbers = [2,3,5,7,13]
    iex-> 5 in prime_numbers
    iex-> true
    iex -> new_primes = List.replace_at(prime_numbers, 0, 11)
    iex -> [11, 3, 5, 7, 13]
    p.s : new item should always be pushed to top and pop from the top of a list for efficiency being linked list in nature. i.e [1,2,3] is represented in elixir as : [1 |[2 |[3|[] ] ] ]. ‘hd’ and ‘tl’ can be used to fetch head and tail of the list.
  7. Maps : A map is a key/ value pair data-type used for dynamically sized key/value structures and manage simple records. ‘%’ is used for declaration of a map followed by {} i.e %{} creates a map.
    empty_map = %{}
    squares = %{1 => 1, 2 => 4, 3 => 9}
    #If keys are atoms, you can write this so it’s slightly shorter:
    iex-> bob = %{name: “Bob”, age: 25, works_at: “Initech”}
    iex-> bob.age
    or
    iex-> bob[:age]
    results to same
    iex -> 25

Complex Data-types in Elixir:

  1. Range: A range is an abstraction that allows you to represent a range of numbers. iex(1)> range = 1..10
  2. Keyword Lists:“A keyword list is a special case of a list, where each element is a two-element tuple, and the first element of each tuple is an atom. The second element can be of any type. Let’s look at an example:”
    days = [{:monday, 1}, {:tuesday, 2}, {:wednesday, 3}]
    #it is equivalent to condensed form as belows:
    days = [monday: 1, tuesday: 2, wednesday: 3]
  3. MapSet: A MapSet is the implementation of a set — a store of unique values, where a value can be of any type.
    days = MapSet.new([:monday, :tuesday, :wednesday])
  4. Time and Date : ~D sigil and ~T sigil is used for date and time creation respectively and combination of both is NaiveDateTime represented with ~N.
    date = ~D[2018–01–31]
    time = ~T[11:59:12.00007]
    date_time = ~N[2000–01–01 23:00:07]

P.S: “A keyword list can contain multiple values for the same key. In addition, you can control the ordering of keyword list elements — something that isn’t possible with maps”

IO:

“gets” method is used for getting user input.
name = IO.gets(“Your name”)

“puts” method is used for giving output to user.
IO.puts(“Your name is #{name}”)

Note: IO methods are used for writing and reading from files too.

Understanding runtime behavior of code:

“Regardless of how you start the runtime, an OS process for the BEAM instance is started, and everything runs inside that process. This is true even when you’re using the iex shell. If you need to find this OS process, you can look it up under the name beam.

Once the system is started, you run some code, typically by calling functions from modules. How does the runtime access the code? The VM keeps track of all modules loaded in memory. When you call a function from a module, BEAM first checks whether the module is loaded. If it is, the code of the corresponding function is executed. Otherwise the VM tries to find the compiled module file — the bytecode — on the disk and then load it and execute the function.”

Starting the runtime of elixir program:

  1. Interactive shell : In your cli :
    > iex
  2. Running script:
    > elixir my_source.ex
  3. Mix shell: to build a production-ready system, mix is your best option.
    >mix new project_name
    > cd project_name && mix compile
    >mix test

Phew ! Thats the wrap-up for the chapter 2 discussing the essential understanding of the elixir system, getting started and building blocks of elixir code.

Chapter 3:

Pattern matching :

One of the most important construct in elixir and the best possible way to remove the clutter and improve the readability of the code is pattern matching.

Matching tuples:

EX:1
> person = {"Sasa", 40}
> {name, age} = person
# name = "Sasa" and age = 40
Ex:2
> person ={"Sasa", 40, "Germany"}
> {name, age, _ } = person
#The number of item on the left much match with the numbers in right

Matching Constant:

Ex:1 
> person = {:desc , "Sasa", 40}
> {:desc, name, age } = person
#The atom/constant i.e :desc must match on the left

Matching lists:

Ex:1
> person = ["Sasa", 40 ]
> [name, age] = person.
#name = "Sasa" and age = 40
Ex:2
> person = ["sasa", 40, "germany"]
> [name | _tail ] = person
#If we want only selective item of list. here name = "sasa" and _tail = [40, "germany"]

Matching maps:

Ex:1
> person = %{name: "Sasa", age: 40}
> %{age: person_age} = person
#person_age = 40

Matching bitStrings and binaries:

Ex:1
> real_number = <<1, 2, 3>>
> <<r1, rest :: binary >> = real_number
#r1 = 1 and binary = <<2, 3>>

Hybrid matching:

iex> [_, {name, _}, _] = [{"Bob", 25}, {"Alice", 30}, {"John", 35}]
iex> {_, {hour, _, _}} = :calendar.local_time()

MultiClause Function

Function overloading with same function name but different arity is way of defining multiclause function in elixir. The pattern is match with the function arity number and type when invoked.

defmodule Geometry do
def area({:rectangle, a, b}) do ①
a * b
end
def area({:square, a}) do ②
a * a
end
def area({:circle, r}) do ③
r * r * 3.14
end
end

iex> Geometry.area({:rectangle, 4, 5})
20
iex> Geometry.area({:square, 5})
25
iex> Geometry.area({:circle, 4})
50.24

Note: You can also write multiclause function for lambda functions.

Guards:

Guards are the condition for execution of the function. Function with same name and arity but needs different operation for different condition that is when guards are useful. “when” module is used to creating guards to functions.

defmodule DataType dodef compare(a, b \\ :no_guess)def compare(a, b) when a == b, do:"Equal"def compare(a, b) when abs(a - b) == 1, do: "One more or less"def compare(a, b) when a - b > 1, do: "Less"def compare(a, b) when b - a > 1, do: "Greater"end

Conditions and Branching :

  1. if :
if(condition) do
#something here
else
#for the condition not satisfied
end

2. cond :

cond do
expression_1 ->
expression_2 ->
true -> #default
end

3. case :

case expression/variable do
pattern_1 ->
pattern_2 ->
end

Loop and Itteration:

Loop is achieved using recursion method in elixir. Multi clause function is used for trapping the stopping condition of the itteration. Eg:

defmodule ListHelper do
def sum([]), do: 0
def sum([head | tail]) do
head + sum(tail)
end
end

The recursion in which the last statement is calling a function is called tail recursion function. Tail recursion function is optimized by erlang such that additional resources is not consumed by stacking the recursive calls.

defmodule ListHelper do
def sum(list) do
do_sum(0, list) #calling function at the end
end
defp do_sum(current_sum, []) do
current_sum
end
defp do_sum(current_sum, [head | tail]) do
new_sum = head + current_sum
do_sum(new_sum, tail)
end
end

Note: Tail recursion is the only possible way to achieve infinite loop .Non-tail recursion often looks more elegant and concise, and it can in some circumstances yield better performance.

Enumerables:

Enum is the library to perform operations on different data types of elixir.
The library is huge and some of the operation in enums are :

  1. Enum.map( data_type, fn x -> #some_operation end ) : It always returns the list of the same size of the data supplied for the operation.
  2. Enum.filter( data_type, fn x -> filter_condition end) : It returns lists of data satisfying the conditions.
  3. Enum.reduce( data_type,initial_acc_value, fn x, acc -> some_operation end ) : It returns value of accumulator i.e acc instead of lists unlike the above.

Comprehension:

The construct that helps to iterate and transform, join and filter different enumerables.

for x <- [1, 2, 3] , y <- ['a', 'b', 'c'] do
{x,y}
end

The for comprehension returns a list of the return type. The O/P for above program is:

[{1,'a'}, {1, 'b'}, {1, 'c'},
{2,'a'}, {2, 'b'}, {2, 'c'},
{3,'a'}, {3, 'b'}, {3, 'c'}
]

Stream:

Streams are a special kind of enumerable that can be useful for doing lazy composable operations over anything enumerable.

Since enumerable, comprehension are extensive topic they are better to have looked in the documentation.

--

--

Redmen Ishab

Software Engineer. Failed startup “Software Factory”. Working experience as CTO, SSE, Full Stack Dev, Mobile App Engineer, and Lecturer.