Starting and changing mindset with Elixir
Created in 2012, Elixir is functional and dynamic language, which make a real option if we want to create a web application. Since, Elixir runs under Erlang virtual machine (BEAM), which make it really stable and mature.
Created by José Valim, who is a Ruby on Rails enthusiastic and had helped the community a lot. So, he took the the best of Rails and merge it with the efficiency of Elixir + Erlang.
Elixir is special for high availability systems .
Functional
The first two meanings we have to forget are objects and classes. It’s all about functions that operate and transform data, pure functions. But, We can not rely on Elixir being a pure functional language.
Dynamic
Elixir variables don’t need to be declared as an specifics data type, It checks at runtime.
Inmutable
Data structure won’t change. For example:
# Uses the match operator to give a true on function
name = “Han Solo”
# It creates a total new value for the new variable
name = “Luke Skywalker”
Concurrency
This is something that Elixir can sell without any help, It’s a big feature here. Since, Elixir runs under BEAM, concurrency is much easier. Code running simultaneously, Thanks Erlang !
Reliability
Elixir is a young language, but It runs on Erlang VM, which is pretty old and a reliable system, one of he most around the globe.
Easy to read and write
By taking some Ruby stuff, Elixir decided to go with the syntax, which is good for us. The code is easy to read and o write.
Modules or classes ?
As I mentioned before classes are not considered in Elixir, every function is stored and handled in a modules, as their namespace.
defmodule
HelloModule do def say_hi do IO.puts "Hello World !" end end
Structs
This is a map where we set up a set of keys and their default values. It’s defined in the module.
defmodule
Userdo
defstruct name: "John", roles: []
end
iex> %User{name: "Han", roles: [:pilot, :shooter]}
User{name: "Steve", roles: [:pilot, :shooter]}
Strings
A string a basically a sequence of bytes, UTF-8 encoded binary. Elixir provides to us a set of functions to interact with our strings.
Lenght.
length/1
It returns the number of bytes in our string.
iex> String.length "Walter White"
12
Replace
replace/3
It returns a new string, It receives three parameters, the string to be changed, the pattern to be replaced and the replacement string.
iex> String.replace("Seed","e","i")
"Siid"
Duplicate
duplicate/2
It returns a specific string duplicated the number of times seted
iex> String.
duplicat("Hello",3)
"Hello Hello Hello "
Split
split/2
Ir returns a list based on the patter of split.
iex> String.
split("Hello, I'm Bond, James Bond", ",")
["Hello", " I'm Bond", " James Bond"]
Collections
List
A simple collection of values, where there can be any type of data.
iex> ["String", 12.3 , :atom]
["String", 12.3, :atom]
List Concatenation
++/2
iex> ["Luke", "Leia"] ++ ["Han"]
["Luke", "Leia", "Han"]
List Subtraction
–/2
iex> ["String", 12.3 , :atom] -- [:atom]
["String", 12.3]
Head / Tail
Heads is the first element of our list and the tail is te remaining elements on the list.
iex> hd ["Head", 222, :tail]
"Head"
iex> tl ["Head", 222, :tail]
[222, :tail]
Keyword Lists
It’s an associative list composed by two tuples, where the key have to be an atom data type, they are ordered and keys can be given more than once.
iex> [foo: "bar", hello: "world"]
[foo: "bar", hello: "world"]
iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list ++ [{:c, 3}]
[a: 1, b: 2, c: 3]
Maps
Maps are more flexible compare to keyword lists, keys can be any value, included variables and maps keys doesn’t follow any kind of ordering.
iex> key = :one
iex> list = %{key => 1, :two => 2, "three" => 3, 4 => 4}
%{:one => 1, :two => 2, "three" => 3, 4 => 4}
# Get an specific value from a given key.
iex> Map.get(list, key)
1
# Add a tuple to the current list.
iex> Map.put(list, :five, 5)
%{:one => 1, :two => 2, "three" => 3, 4 => 4, :five => 5}
# Return the map on list format.
iex> Map.to_list(list)
[{4, 4}, {:atom, 1}, {:five, 5}, {:two, 2}, {"three", 3}]
Enum
It is a set of algorithms to be used over collections. In this section we will just show some of them. You can check all of them over here.
All
all?/2
We supply a fn(x) where will be run for all items on our collection. Will return true if all invocations returns true, with just one that returns false the entire method will return false.
iex> Enum.all?([1,2,3], fn(number) -> number < 5 end )
true
iex> Enum.all?([1,2,3], fn(number) -> number < 2 end )
false
Any
any?/2
We supply a fn(x) where will be run for all items on our collection. Will return true if at list one invocation returns true, otherwise will return false.
iex> Enum.any?([1,2,3], fn(number) -> number < 2 end )
true
iex> Enum.any?([1,2,3], fn(number) -> number == 5 end )
false
Chunk By
chunk_by/2
Specially if we need to group our collections based in a given function.
iex> Enum.chunk_by(["one", "two", "three", "four", "five"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"]]
Each
each/2
Invokes the given function for each item on the collection. It returns an atom :ok
iex> Enum.each(["one", "two", "three"], fn(x) -> IO.puts(x) end)
one
two
three
:ok
Map
map/2
Invokes the given function for each item on the collection. It returns a new collection with new values.
iex> Enum.map(["one", "two"], fn(x) -> String.upcase(x) end)
["ONE", "TWO"]
Member
member?/2
Checks if an item exists in a collection.
iex> Enum.member?(["one", "two", "three"], "three")
true
Reject
reject/2
Return a new collection of items that returns false from the given fn(x).
iex> Enum.reject([1,2,3,4,5,6], fn(x) -> Integer.is_even(x) end)
[1, 3, 5]
Sort
sort/2
It sorts the collection by the given fn(x).
iex> Enum.sort([%{:val => 2}, %{:val => 3}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 3}, %{val: 2}, %{val: 1}]
Unique By
unique_by/2
Remove all duplicated into our collection
iex> Enum.uniq([1, 2, 3, 2, 1])
[1, 2, 3]
Pipe Operator
The pipe operator
|>
passes the result of an expression as the first parameter of another expression.
When I was using elixir by the first time this operator called my attention immediately. Since functional programming is about sending data and transforming it thought functions it can get really messy, but the pipe operator is something to help us on that job.
Our problem:
# Return the final value of a product
formated_price(taxes(commision(product_value(), 0.01), 0.13))
Some OO solution:
prod_val = product_value()
prod_commision = commision(prod_val, 0.01)
prod_taxes = taxes(prod_commision, 0.13)
prod_final_value = formated_price(prod_taxes)
prod_final_value
Our Elixir Solution:
product_value()
|> commision(0.01)
|> taxes(0.13)
|> formated_price()
As you can see every return value of a function is passed as the first parameter of the following function. It makes our code really easy to read.
Pattern Matching. Isn’t it just assignment?
This is a deep functionality in Elixir. To understand this a little bit more I have to say that = operator doesn’t necessarily means “assign something to a variable”, instead it really means “match the left hand side to the right hand side”. It turns the whole expression into a equation.
iex> x = 1
1
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1
Useful approaches
Pattern matching becomes really useful when we use it with tuples, functions or recursion.
# We want to assign to match value with te number 2
iex> {:ok, value} = {:ok, 2}
{:ok, 2}
iex> value
2
# If the atom value :ok doesn't match It will return a fail
iex> {:ok, value} = {:none, 2}
** (MatchError) no match of right hand side value: {:none, 2}
It seems really cool, but check this, something more functional
defmodule Greet do
def say_hi(tuple) do
case tuple do
{:morning, name} ->
"Morning #{name} !"
{:evening, name} ->
"Good evening #{name} !"
_ ->
"Default Hi !!"
end
end
end
iex> Greet.say_hi({:morning, "Luke"})
"Morning Luke !"
iex> Greet.say_hi({:morning, "Han"})
"Morning Han !"
iex> Greet.say_hi({:defaut, "Nobody"})
"Default Hi !!"
Finally, the more useful approach to me is by using Pattern Matching on functions definitions:
defmodule Greet do
def say_hi(:morning, name) do
"Morning #{name} !"
end
def say_hi(:evening, name) do
"Good Evening #{name} !"
end
def say_hi(name) do
"Default Hi #{name} !"
end
end
iex> Greet.say_hi(:morning, "Luke")
"Morning Luke !"
iex> Greet.say_hi(:morning, "Han")
"Morning Han !"
iex> Greet.say_hi("Leila")
"Default Hi Leila !"
Conclusion
To me, having a Ruby on Rails background, Elixir seems pretty nice and I’m looking forward go deeper and deeper into this language. This entrance is part of an introduction We did in 4geeks, you can check the full video: