Андрей Дренски
#HSLIDE
#HSLIDE
#HSLIDE
if <condition> do
<do_stuff>
else
<do_other_stuff>
end
#HSLIDE
def fib(n) when is_integer(n) and n >= 0 do
if n == 0 or n == 1 do
n
else
fib(n-1) + fib(n-2)
end
end
#HSLIDE
if
си има и брат-близнак - unless
.
Той обаче не е по-малко зъл!
Следните две парчета код са еквивалентни:
if <condition> do
<do_stuff>
else
<do_other_stuff>
end |
unless <condition> do
<do_other_stuff>
else
<do_stuff>
end |
#HSLIDE
Може и по-кратко:
if <condition>, do: <do_stuff>, else: <do_other_stuff>
unless <condition>, do: <do_other_stuff>, else: <do_stuff>
Тъй като if
и unless
са макроси, които приемат "клаузите" си като (почти) key-value двойки и ги оценяват мързеливо, може клаузата else
да бъде изпусната.
#HSLIDE
#HSLIDE
cond do
<condition1> -> <do_stuff>
<condition2> -> <do_other_stuff>
...
<conditionN> -> <do_whatever_you_want>
end
#HSLIDE
def fib(n) do
cond do
n == 0 or n == 1 -> n
true -> fib(n-1) + fib(n-2)
end
end
#HSLIDE
#HSLIDE
case <expr> do
<pattern1> -> <do_stuff>
<pattern2> -> <do_other_stuff>
...
<patternN> -> <do_whatever_you_want>
end
#HSLIDE
def fib(n) do
case n do
0 -> 0
1 -> 1
n when is_integer(n) and n > 1 -> fib(n-1) + fib(n-2)
end
end
#HSLIDE
... and mighty pattern matching:
def fib(0) do: 0
def fib(1) do: 1
def fib(n) when is_integer(n) and n > 1, do: fib(n-1) + fib(n-2)
#HSLIDE
Използвайте почти винаги pattern matching, а където не е удобно, може еквивалентния case
.
#HSLIDE
##Exceptions
#HSLIDE
Част от общата концепция на Elixir и Erlang е грешките да бъдат фатални и да убиват процеса, в който са възникнали. Нека някой друг процес (supervisor) се оправя с проблема.
Например - ако работим с файлове, задачата на една нишка е просто да го отвори и прочете - какво се случва, ако файлът липсва (а не трябва), е проблем на някоя друга нишка.
#HSLIDE
В Elixir грешките (изключенията) са *Error
- FunctionClauseError
, MatchError
, RuntimeError
и около 30 други.
Всеки вид изключение има свое съобщение (стринг), което се достъпва с .message
Нека се върнем назад до примерите с fib(n)
...
#HSLIDE
Изключения се вдигат с raise
по няколко начина...
iex> raise "oops!" # просто съобщение
** (RuntimeError) oops!
iex> raise RuntimeError # изричен тип грешка
** (RuntimeError) runtime error
iex> raise RuntimeError, message: "oops!" # тип + съобщение
** (RuntimeError) oops!
#HSLIDE
...но се хващат по един начин с rescue
и pattern matching.
try do
<do_something_dangerous>
rescue
[FunctionClauseError, RuntimeError] -> IO.puts "normal stuff"
e in [ArithmeticError] -> IO.puts "Do the math: #{e.message}"
other ->
IO.puts "IDK what you did there..."
raise other, [message: "too late, we're doomed"], System.stacktrace
after # optional
IO.puts "whew"
end
#HSLIDE
На практика try/rescue
конструкцията също се използва рядко. Алтернативата е - познайте - pattern matching!
Доста от стандартните функции имат два варианта.
iex> File.read "no_such.file"
{:error, :enoent}
iex> File.read "existing.file"
{:ok, "file contents, iei"}
iex> File.read! "no_such.file"
** (File.Error) could not read ... нещо си
iex> File.read! "existing.file"
"file contents, iei"
#HSLIDE
def checkFile(str) when is_binary(str) do
case File.read str do
{:ok, body} -> IO.puts "iei" # тук обработваме body
{:error, reason} -> IO.puts "Oops: #{reason}"
end
end
def checkFile!(str) when is_binary(str) do
body = File.read! str # може да хвърли File.Error
IO.puts "I am #{String.length(body)} bytes long!"
end
def checkFile2(str) when is_binary(str) do
{:ok, body} = File.read str # НЕ правете така
end
#HSLIDE
Да дефинираме свое изключение (и да спасим принцесата):
defmodule PrincessError do
# message - задължително, други полета са по избор
defexception message: "save me!", name: "Alex"
def oops do
try do
raise PrincessError
rescue
pr in PrincessError ->
IO.puts "Caught #{pr.name} - she said \"#{pr.message}\""
end
end
end
iex> PrincessError.oops
Caught Alex - she said "save me!"
:ok
#HSLIDE
Можем и да "хвърляме" всякакви други неща с throw
:
def foo do
b = 5
throw b
IO.puts "this won't print"
end
def bar do
try do: foo(),
catch
:some_atom -> IO.puts "Caught #{:some_atom}!"
x when is_integer(x) -> IO.puts "Caught int x = #{x}!"
end
end
iex> bar
Caught int x = 5!
#HSLIDE
Нещо още по-рядко (г/д колкото трикрак еднорог):
try do
exit "I am exiting" # така можем да излезем от процес
catch
:exit, _ -> IO.puts "not really"
end # и процесът всъщност остава жив
#HSLIDE
... но catch
e лош стил и никога няма да ни се налага (и няма) да го правим.
#HSLIDE
if, unless, cond
try/catch
try/rescue
#HSLIDE
#HSLIDE
iei
#HSLIDE
#HSLIDE
В Elixir има множество "специални форми" - синтактични конструкции, които използваме наготово, но не е ясно как се "оценяват".
Те не са имплементирани на Elixir, следователно не могат да бъдат предефинирани. Винаги се импортират от Kernel. Например:
#HSLIDE
% # създава структура (по име и, евентуално, полета)
%{} # създава празен Map, реално еквивалентно на Map.new
&(expr) # създава анонимна функция
&(&1*&1)
&[&1|&2]
&{&2,String.length(&1)}
&(:foo) # трябва да има поне един placeholder (тези &1, &2,...)
{args} # създава наредена n-торка (tuple)
#HSLIDE
<<args>> # създава bitstring
left :: right # указва опции за bitstring
left . right # създава alias или извиква функция от alias
# зависи дали right е в кавички или с каква буква започва
left = right # pattern matching
^var # pin оператор - pattern matching без променяне на стойността
alias, case, cond, (fn..end), for, import,
receive, require, try, quote, unquote, with и други...
#HSLIDE
Какво не е специална форма, ами макрос, годен за предефиниране:
and, or, not, !, &&, ||, .., <>
def, defp, defmacro, defmacrop, defstruct
if, in, is_*, raise, |> и други...