Get the code: learnelixir.ex
Elixir este un limbaj funcțional modern construit pe baza mașinii virtuale Erlang. E total compatibil cu Erlang, dar are o sintaxă mai prietenoasă și propune mai multe posibilități.
# Comentariile de o linie încep cu simbolul diez.
# Pentru comentarii pe mai multe linii nu există sintaxă separată,
# de aceea folosiți mai multe linii cu comentarii.
# Pentru a folosi shell-ul Elixir utilizați comanda `iex`.
# Compilați modulele cu comanda `elixirc`.
# Ambele comenzi vor lucra în terminal, dacă ați instalat Elixir corect.
## ---------------------------
## -- Tipuri de bază
## ---------------------------
# Numere
3 # număr întreg
0x1F # număr întreg
3.0 # număr cu virgulă mobilă
# Atomii, sunt constante nenumerice. Ei încep cu `:`.
:salut # atom
# Tuplele sunt păstrate în memorie consecutiv.
{1,2,3} # tuple
# Putem accesa elementul tuplelui folosind funcția `elem`:
elem({1, 2, 3}, 0) #=> 1
# Listele sunt implementate ca liste înlănțuite.
[1,2,3] # listă
# Fiecare listă ne vidă are cap (primul element al listei)
# și coadă (restul elementelor).
# Putem accesa capul și coada listei cum urmează:
[cap | coadă] = [1,2,3]
cap #=> 1
coadă #=> [2, 3]
# În Elixir, ca și în Erlang, simbolul `=` denotă potrivirea șabloanelor și
# nu atribuire.
#
# Aceasta înseamnă că expresia din stînga (șablonul) se potrivește cu
# expresia din dreaptă.
#
# În modul acesta exemplul de mai sus lucrează accesînd capul și coada unei liste.
# Potrivirea șablonului va da eroare cînd expresiile din stînga și dreapta nu se
# potrivesc, în exemplu acesta tuplele au lungime diferită.
{a, b, c} = {1, 2} #=> ** (MatchError)
# Există și date binare
<<1,2,3>>
# Sunt două tipuri de șiruri de caractere
"salut" # șir de caractere Elixir
'salut' # listă de caractere Erlang
# Șir de caractere pe mai multe linii
"""
Sunt un șir de caractere
pe mai multe linii.
"""
#=> "Sunt un șir de caractere\npe mai multe linii..\n"
# Șirurile de caractere sunt codificate în UTF-8:
"Bună dimineața" #=> "Bună dimineața"
# Șirurile de caractere sunt date binare, listele de caractere doar liste.
<<?a, ?b, ?c>> #=> "abc"
[?a, ?b, ?c] #=> 'abc'
# `?a` în Elixir întoarce codul ASCII pentru litera `a`
?a #=> 97
# Pentru a concatena listele folosiți `++`, pentru date binare - `<>`
[1,2,3] ++ [4,5] #=> [1,2,3,4,5]
'Salut ' ++ 'lume' #=> 'Salut lume'
<<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>
"Salut " <> "lume" #=> "Salut lume"
# Diapazoanele sunt reprezentate ca `început..sfîrșit` (inclusiv)
1..10 #=> 1..10
început..sfîrșit = 1..10 # Putem folosi potrivirea șabloanelor cu diapazoane de asemenea
[început, sfîrșit] #=> [1, 10]
# Dicţionarele stochează chei şi o valoare pentru fiecare cheie
genuri = %{"Ion" => "bărbat", "Maria" => "femeie"}
genuri["Ion"] #=> "bărbat"
# Dicționare cu chei de tip atom au sintaxă specială
genuri = %{ion: "bărbat", maria: "femeie"}
genuri.ion #=> "bărbat"
## ---------------------------
## -- Operatori
## ---------------------------
# Operații matematice
1 + 1 #=> 2
10 - 5 #=> 5
5 * 2 #=> 10
10 / 2 #=> 5.0
# În Elixir operatorul `/` întotdeauna întoarce un număr cu virgulă mobilă.
# Folosiți `div` pentru împărțirea numerelor întregi
div(10, 2) #=> 5
# Pentru a obține restul de la împărțire utilizați `rem`
rem(10, 3) #=> 1
# Există și operatori booleni: `or`, `and` and `not`.
# Acești operatori așteaptă ca primul argument o expresie booleană.
true and true #=> true
false or true #=> true
1 and true #=> ** (BadBooleanError)
# Elixir de asemenea oferă `||`, `&&` și `!` care acceptă argumente de orice tip.
# Toate valorile în afară de `false` și `nil` se vor evalua ca `true`.
1 || true #=> 1
false && 1 #=> false
nil && 20 #=> nil
!true #=> false
# Operatori de comparație: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<` și `>`
1 == 1 #=> true
1 != 1 #=> false
1 < 2 #=> true
# `===` și `!==` au strictețe mai mare cînd comparăm numere întregi și reale:
1 == 1.0 #=> true
1 === 1.0 #=> false
# Putem compara de asemenea și date de diferite tipuri:
1 < :salut #=> true
# La compararea diferitor tipuri folosiți următoare prioritate:
# număr < atom < referință < funcție < port < proces < tuple < listă < șir de caractere
# Cităm pe Joe Armstrong în acest caz: "Ordinea actuală nu e importantă,
# dar că ordinea totală este bine definită este important."
## ---------------------------
## -- Ordinea execuției
## ---------------------------
# expresia `if`
if false do
"Aceasta nu veți vedea niciodată"
else
"Aceasta veți vedea"
end
# expresia opusă `unless`
unless true do
"Aceasta nu veți vedea niciodată"
else
"Aceasta veți vedea"
end
# Țineți minte potrivirea șabloanelor? Multe structuri în Elixir se bazează pe ea.
# `case` ne permite să comparăm o valoare cu multe șabloane:
case {:unu, :doi} do
{:patru, :cinci} ->
"Aceasta nu se potrivește"
{:unu, x} ->
"Aceasta se potrivește și atribuie lui `x` `:doi` în acest bloc"
_ ->
"Aceasta se va potrivi cu orice valoare"
end
# Simbolul `_` se numește variabila anonimă.
# Folosiți-l pentru valori ce nu vă interesează.
# De exemplu, dacă doar capul listei ne intereseaza:
[cap | _] = [1,2,3]
cap #=> 1
# Pentru o citire mai bună putem scri:
[cap | _coadă] = [:a, :b, :c]
cap #=> :a
# `cond` ne permite să verificăm multe condiții de odată.
# Folosiți `cond` în schimbul la multe expresii `if`.
cond do
1 + 1 == 3 ->
"Aceasta nu veți vedea niciodată"
2 * 5 == 12 ->
"Pe mine la fel"
1 + 2 == 3 ->
"Aceasta veți vedea"
end
# Este obușnuit de setat ultima condiție cu `true`, care se va potrivi întotdeauna.
cond do
1 + 1 == 3 ->
"Aceasta nu veți vedea niciodată"
2 * 5 == 12 ->
"Pe mine la fel"
true ->
"Aceasta veți vedea (este else în esență)"
end
# Blocul `try/catch` se foloște pentru prelucrarea excepțiilor.
# Elixir suportă blocul `after` care se execută în orice caz.
try do
throw(:salut)
catch
mesaj -> "Am primit #{mesaj}."
after
IO.puts("Sunt în blocul after.")
end
#=> Sunt în blocul after.
# "Am primit salut"
## ---------------------------
## -- Module și Funcții
## ---------------------------
# Funcții anonime (atenție la punct la apelarea funcției)
square = fn(x) -> x * x end
square.(5) #=> 25
# Ele de asemenea aceptă multe clauze și expresii de gardă.
# Expresiile de gardă vă permit să acordați potrivirea șabloanelor,
# ele sunt indicate după cuvîntul cheie `when`:
f = fn
x, y when x > 0 -> x + y
x, y -> x * y
end
f.(1, 3) #=> 4
f.(-1, 3) #=> -3
# Elixir de asemenea oferă multe funcții incorporate.
# Ele sunt accesibile în scopul curent.
is_number(10) #=> true
is_list("salut") #=> false
elem({1,2,3}, 0) #=> 1
# Puteți grupa cîteva funcții într-un modul. În interiorul modulului folosiți `def`
# pentru a defini funcțiile necesare.
defmodule Math do
def sum(a, b) do
a + b
end
def square(x) do
x * x
end
end
Math.sum(1, 2) #=> 3
Math.square(3) #=> 9
# Pentru a compila modulul nostru simplu Math îl salvăm ca `math.ex` și utilizăm `elixirc`.
# în terminal: elixirc math.ex
# În interiorul modulului putem defini funcții cu `def` și funcții private cu `defp`.
defmodule PrivateMath do
# O funcție definită cu `def` este accesibilă pentru apelare din alte module,
def sum(a, b) do
do_sum(a, b)
end
# O funcție privată poate fi apelată doar local.
defp do_sum(a, b) do
a + b
end
end
PrivateMath.sum(1, 2) #=> 3
PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)
# Declarația funcției de asemenea suportă expresii de gardă și multe clauze:
defmodule Geometry do
def area({:rectangle, w, h}) do
w * h
end
def area({:circle, r}) when is_number(r) do
3.14 * r * r
end
end
Geometry.area({:rectangle, 2, 3}) #=> 6
Geometry.area({:circle, 3}) #=> 28.25999999999999801048
Geometry.area({:circle, "not_a_number"}) #=> ** (FunctionClauseError)
# Din cauza variabilelor imutabile, un rol important îl ocupă funcțiile recursive
defmodule Recursion do
def sum_list([head | tail], acc) do
sum_list(tail, acc + head)
end
def sum_list([], acc) do
acc
end
end
Recursion.sum_list([1,2,3], 0) #=> 6
# Modulele în Elixir suportă atribute, există atribute incorporate și
# puteți adăuga altele.
defmodule MyMod do
@moduledoc """
Este un atribut incorporat
"""
@my_data 100 # Acesta e atributul nostru
IO.inspect(@my_data) #=> 100
end
# Operatorul |> permite transferarea rezultatului unei expresii din stînga
# ca primul argument al unei funcții din dreapta.
Range.new(1,10)
|> Enum.map(fn x -> x * x end)
|> Enum.filter(fn x -> rem(x, 2) == 0 end)
#=> [4, 16, 36, 64, 100]
## ---------------------------
## -- Structuri și Excepții
## ---------------------------
# Structurile sunt extensii a dicționarelor ce au valori implicite,
# verificări în timpul compilării și polimorfism
defmodule Person do
defstruct name: nil, age: 0, height: 0
end
joe_info = %Person{ name: "Joe", age: 30, height: 180 }
#=> %Person{age: 30, height: 180, name: "Joe"}
# Acesarea cîmpului din structură
joe_info.name #=> "Joe"
# Actualizarea valorii cîmpului
older_joe_info = %{ joe_info | age: 31 }
#=> %Person{age: 31, height: 180, name: "Joe"}
# Blocul `try` cu cuvîntul cheie `rescue` e folosit pentru a prinde excepții
try do
raise "o eroare"
rescue
RuntimeError -> "a fost prinsă o eroare runtime"
_error -> "aici vor fi prinse toate erorile"
end
#=> "a fost prinsă o eroare runtime"
# Toate excepțiile au un mesaj
try do
raise "o eroare"
rescue
x in [RuntimeError] ->
x.message
end
#=> "o eroare"
## ---------------------------
## -- Concurența
## ---------------------------
# Concurența în Elixir se bazează pe modelul actor. Pentru a scrie programe
# concurente avem nevoie de trei lucruri:
# 1. Crearea proceselor
# 2. Trimiterea mesajelor
# 3. Primirea mesajelor
# Un nou proces se crează folosind funcția `spawn`, care primește o funcție
# ca argument.
f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>
spawn(f) #=> #PID<0.40.0>
# `spawn` întoarce identificatorul procesului pid, îl puteți folosi pentru
# a trimite mesaje procesului. Mesajele se transmit folosind operatorul `send`.
# Pentru primirea mesajelor se folosește mecanismul `receive`:
# Blocul `receive do` este folosit pentru așteptarea mesajelor și prelucrarea lor
# cînd au fost primite. Blocul `receive do` va procesa doar un singur mesaj primit.
# Pentru a procesa mai multe mesaje, funcția cu blocul `receive do` trebuie
# recursiv să se auto apeleze.
defmodule Geometry do
def area_loop do
receive do
{:rectangle, w, h} ->
IO.puts("Aria = #{w * h}")
area_loop()
{:circle, r} ->
IO.puts("Aria = #{3.14 * r * r}")
area_loop()
end
end
end
# Compilați modulul și creați un proces
pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0>
# Un alt mod
pid = spawn(Geometry, :area_loop, [])
# Trimiteți un mesaj către `pid` care se va potrivi cu un șablon din blocul `receive`
send pid, {:rectangle, 2, 3}
#=> Aria = 6
# {:rectangle,2,3}
send pid, {:circle, 2}
#=> Aria = 12.56000000000000049738
# {:circle,2}
# Interpretatorul este de asemenea un proces, puteți folosi `self`
# pentru a primi identificatorul de proces:
self() #=> #PID<0.27.0>
## ---------------------------
## -- Agenții
## ---------------------------
# Un agent este un proces care urmărește careva valori ce se schimbă.
# Creați un agent cu `Agent.start_link`, transmițînd o funcție.
# Stare inițială a agentului va fi rezultatul funcției.
{ok, my_agent} = Agent.start_link(fn -> ["roșu", "verde"] end)
# `Agent.get` primește numele agentului și o `fn` care primește starea curentă
# Orice va întoarce `fn` este ceea ce veți primi înapoi:
Agent.get(my_agent, fn colors -> colors end) #=> ["roșu", "verde"]
# Actualizați starea agentului în acelaș mod:
Agent.update(my_agent, fn colors -> ["albastru" | colors] end)
Got a suggestion? A correction, perhaps? Open an Issue on the GitHub Repo, or make a pull request yourself!
Originally contributed by Joao Marques, and updated by 3 contributors.