Elixir Basic
Bài đăng này đã không được cập nhật trong 6 năm
What is Elixir?
Elixir is a dynamic, functional language that build on top of Erlang Virtual Machine, a distributed and fault-tolerant systems with low-latency. It was designed for building scalable and maintainable web applications as well as embedded software. Elixir wrap functional programming with immutable state and an actor-based approach to concurrency.
All About Transforming Data
If you come from an object-oriented world, then you are used to thinking in terms of classes and their instances. A class defines behavior, and objects hold state. By invoking a method on an object and passing it other object we update the object's status. In the world of OOP data hiding is our goal.
But somethime we don't want to model a complext and abstract class hierarchies, we just wanted to get thing done. For example somewhere down the line I want to read a text file, doing calculation on its content, write it back as csv file and send it back as an HTTP response. I don't want to hide data. I want to transform it, combine with pipline and immutable state making it likes bread and butter.
Installing Elixir
Before we dive into elixir, lets install it. Go to https://elixir-lang.org/install.html and follow instruction for your specific OS. To confirm that you have elixir up and running type this into your terminal.
$ elixir -v
And you should be something similar to this
$ Erlang/OTP 20 [erts-9.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Elixir 1.5.2
Basic Types
Elixir's build-in types are: Integer, Float, Boolean, Atom, String, List, Map, Tuple and Function. There are also system type like PID, ports and references.
iex> 12 # integer
iex> 1.15 # float
iex> true # boolean
iex> :name # atom
iex> "string" # string
iex> "Hello #{name}" # string interpolation
iex> [1, 2, 3, 4] # list
iex> {1, 2, 3} # tuple
iex> %{a: 10, b: 20} # map
iex> %{"a" => 10, "b" => 20} # map
iex> add = fn a, b -> a + b end # anonymous function
Boolean
Bollean is actually an atom with the name true and false respectively
iex> :true == true
true
iex> :false == false
true
List
Because list implemented as linked list we can't randomly access element of a list using regular indexing syntax list we used to. To get the first element of the list we can use the [head | tail] syntax with head being the first element and tail is a list rest of remaning elements.
iex> [h | t] = [1, 2, 3, 4, 5]
iex> h
1
iex> t
[2, 3, 4, 5]
we can use this to create a function that can help us get element at any given index, but fortuantely elixir has a build-in module that deal with this problem called Enum
iex> Enum.at([1,2,3], 2)
3
Tuple
Tuple is just like list except elements are store contiguously in memory. Elixir use curly brackets syntax to define tuple
iex> t = {:ok, "I'm OK"}
{:ok, "I'm OK"}
iex> elem(t, 0)
:ok
iex> put_elem(t, 0, :error)
{:error, "I'm OK"}
Map
Maps are the key-value data structure just like Hash in ruby. For example
iex> m = %{:name => "Jonh", :like => "Apple"}
iex> Map.keys(m)
[:name, :like]
iex> Map.values(m)
["Jonh", "Apple"]
iex> m[:name]
"Jonh"
iex> m.name
"Jonh"
iex> Map.drop(m, [:like])
%{name: "Jonh"}
When a key in the map is atom we can use special syntax like this
iex> m = %{a: 10, b: 20}
%{a: 10, b: 20}
The simplest way to update a map is with this syntax
iex> m = %{a: 10, b: 20}
%{a: 10, b: 20}
iex> m1 = %{m | b: 30}
%{a: 10, b: 30}
Keyword List
This is a special kind of list that each element is a tuple of two elements. The first element of the tuple is a key and second is a value. The key for keyword list must be atom type.
iex> l = [a: 10, b: 20]
iex> [a, b] = l # a = {:a, 10}, b = {:b, 20}
Pattern Matching
Let's fire up the interactive elixir shell by typing iex in your terminal and type in the really simple code like below.
iex> x = 10
iex> y = 15 + x
Looking at this code snippet most of us would think that we've just assigned 10 to x , adding 15 to x and assigned the result back to y, but this is not the case in elixir. The equal sign is not an assignment, but instead it's like an assertion. Elixir call the = sign a match operator. If elixir can find a way of making the left hand side equal to the right hand side value then it's success otherwise it's gonna blow up. In the case of the first line because x is a variable elixir try to match by bind value 10 to this variable as a result x get the value of 10.
iex> x = 10
iex> 20 = x
** (MatchError) no match of right hand side value: 10
If your really not convince that it is a match operator then lets look at this example. In most programming language this will result in syntax error, but as you can see it works perfectly fine in elixir.
iex> a = 1
iex> 1 = a
1
Matching doesn't limit to just simple value we can also use it to match list, tuple, keyword list, map. For example
iex> [a, b] = [1, [2, 3]] # a = 1, b = [2, 3]
iex> {:ok, value} = {:ok, "I'm OK"} # value = "I'm OK"
iex> %{name: n, age: a} = %{name: "Jonh", age: 25} # n = "Jonh", a = 25
iex> %{name: n, age: a} = %{name: "Jonh"}
** (MatchError) no match of right hand side value: %{name: "Jonh"}
Function
In elixir function must be live inside of a module and define using def keyword or defp for private function. To define a module we use defmodule keyword.
defmodule MyModule do
def my_func(args), do: # inline function
def my_func(args) do
# multi line function
end
end
there is also another kind of function called anonymous function that we've just seen in basic types section. To invoke anonymous function we need to use .() notation.
iex> add = fn a, b -> a + b end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex> add.(10,20)
30
Pattern Match on Function
Remember that assignment is just pattern match in elixir? That is also hold true when it come to function parameters. Let's look at the example on how to count all elements inside a list with recursive function
defmodule MyList do
def len([]), do: 0
def len([_h | t]), do: 1 + len(t)
end
iex> MyList.len([1,2,3,4,5])
5
The second definition of len wii slice down a list one element at a time and goes into a recursion until the list is empty which will then match the definition of the first function. Because the tail of a list is another list excluding the first element so list with one element is always terminate by an empty list. With this trick in your bag you can almost write any complex conditional logic without event using any if condition at all.
Control Flow
If and unless
if and unless is not a language keyword construct, but rather a macro that expand to case statement. It adds syntactic sugar to make it more convenient.
if condition do
# true part
else
# false part
end
unless condition do
# false part
else
# true part
end
case
case lets you test a value against a set of patterns, executes the code associated with the first one that matches, and returns the value of that code. The pat- terns may include guard clauses.
case File.open("text.txt") do
{:ok, file} ->
# sucessfully open a file
{:error, reason} ->
# failed to open file
_ ->
# guard clause if for some reason the above pattern doesn't match
end
Getting Help
You can get help with a nice example written in markdown on a module by using h function in iex like this.
iex> h Enum
Enum
Provides a set of algorithms that enumerate over enumerables according to the
Enumerable protocol.
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
Some particular types, like maps, yield a specific format on enumeration. For
example, the argument is always a {key, value} tuple for maps:
iex> map = %{a: 1, b: 2}
iex> Enum.map(map, fn {k, v} -> {k, v * 2} end)
[a: 2, b: 4]
Note that the functions in the Enum module are eager: they always start the
enumeration of the given enumerable. The Stream module allows lazy enumeration
of enumerables and provides infinite streams.
Since the majority of the functions in Enum enumerate the whole enumerable and
return a list as result, infinite streams need to be carefully used with such
functions, as they can potentially run forever. For example:
Enum.each Stream.cycle([1, 2, 3]), &IO.puts(&1)
to get help on a function you need to specify both function name and it's arity (number of argument)
iex> h Enum.at/2
def at(enumerable, index, default \\ nil)
Finds the element at the given index (zero-based).
Returns default if index is out of bounds.
A negative index can be passed, which means the enumerable is enumerated once
and the index is counted from the end (e.g. -1 finds the last element).
Note this operation takes linear time. In order to access the element at index
index, it will need to traverse index previous elements.
## Examples
iex> Enum.at([2, 4, 6], 0)
2
iex> Enum.at([2, 4, 6], 2)
6
iex> Enum.at([2, 4, 6], 4)
nil
iex> Enum.at([2, 4, 6], 4, :none)
:none
All rights reserved