| theme | uncover |
|---|---|
| style | .small-text { font-size: 0.75rem; letter-spacing: 1px; font-family: "Times New Roman", Tahoma, Verdana, sans-serif; } section { font-size: 28px; letter-spacing: 1px !important; } li { font-size: 28px; letter-spacing: 1px !important; } p.quote { line-height: 38px; } q { font-size: 32px; letter-spacing: 1px !important; } cite { text-align: right; font-size: 28px; margin-top: 12px; margin-bottom: 128px; } code.language-elixir { background: #000; color: #f8f8f8; } section { letter-spacing: 1px !important; } span.hljs-comment, span.hljs-quote, span.hljs-meta { color: #7c7c7c; } .hljs-keyword, .hljs-selector-tag, .hljs-tag, .hljs-name { color: #f96a6a; } .hljs-attribute, .hljs-selector-id { color: #ffffb6; } .hljs-string, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-addition { color: #a8ff60; } .hljs-subst { color: #daefa3; } .hljs-regexp, .hljs-link { color: #e9c062; } .hljs-title, .hljs-section, .hljs-type, .hljs-doctag { color: #f0f05b; } .hljs-symbol, .hljs-bullet, .hljs-variable, .hljs-template-variable, .hljs-literal { color: #ffc57a; } .hljs-number, .hljs-deletion { color:#ff73fd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } |
| paginate | true |
| footer | Курс по Elixir 2023, ФМИ |
| backgroundImage | linear-gradient(to bottom, #E0EAFC, #CFDEF3) |
| marp | true |
- Какво е поведение?
- Какво е OTP?
- GenServer/Supervisor/Application?
Част от общата концепция на Elixir и Erlang е грешките да бъдат фатални и да убиват процеса, в който са възникнали.
Нека някой друг процес (supervisor) се оправя с проблема.
- Грешките в Elixir не са препоръчителни за употреба.
- Имат славата на GOTO програмиране и наистина е хубаво да помислим дали има нужда от тях в дадена ситуация.
- "Let It Crash!" не означава, че оставяме бъгове и проблеми в кода, когато ги намерим.
- Бъгове и проблеми се тестват и оправят.
- Обикновено "Let It Crash!" е свързан с външни ресурси, достъп и неща над които нямаме контрол, ако процесът се рестартира с начален стейт има шанс тази грешка да се оправи.
- За грешно поведение и реакция - match на {:error, reason} и изпълни конкретно действие.
- Прието е функции, при които има проблем да връщат:
{:error, <проблем>}
- Ако се изпълняват с успех ще имат резултат:
{:ok, <резултат>}
- Имената на функции, които биха могли да 'вдигат' грешка, обикновено завършват на '!'.
raise "Ужаст!"
#=> (RuntimeError) Ужаст!raise RuntimeError
#=> (RuntimeError) runtime errorraise ArgumentError, message: "Грешка, брато!"
#=> (ArgumentError) Грешка, брато!try do
1 / 0
rescue
[RuntimeError, ArgumentError] ->
IO.puts("Няма да стигнем до тук.")
error in [ArithmeticError] ->
IO.puts("На нула не се дели, #{error.message}")
any_other_error ->
IO.puts("Лошаво... #{any_other_error.message}")
else
IO.puts("Няма грешка.")
after
IO.puts("Finally!")
enddefmodule VeryBadError do
defexception message: "Лошо!!!"
endtry do
raise VeryBadError
rescue
error in VeryBadError ->
IO.puts(inspect(error, structs: false))
end
#=> %{
#=> __exception__: true,
#=> __struct__: VeryBadError,
#=> message: "Лошо!!!"
#=> }defmodule EvenWorseError do
defexception [:message]
@impl true
def exception(value) do
msg = "An even worst error was raised with value #{inspect(value)}"
%EvenWorseError{message: msg}
end
endtry do
raise EvenWorseError, :my_bad
rescue
error in EvenWorseError ->
IO.puts(error.message)
end- С
throw'подхвърляме' стойност, която може да се 'хване' по-късно:
try do
b = 5
throw b
IO.puts "this won't print"
catch
:some_atom -> IO.puts "Caught #{:some_atom}!"
x when is_integer(x) -> IO.puts(x)
end
#=> 5- Нещо още по-рядко:
try do
exit("I am exiting") # така можем да излезем от процес
catch
:exit, _ -> IO.puts "not really"
end # и процесът всъщност остава жив- В кода на mix няма прихващане на грешки.
- В кода на компилатора на Elixir има само няколко прихващания.
defmodule Quitter do
def run do
Process.sleep(3000)
exit(:i_am_tired)
end
end
Process.flag(:trap_exit, true)
spawn_link(Quitter, :run, [])
receive do msg -> IO.inspect(msg); end
# {:EXIT, #PID<0.95.0>, :i_am_tired}action = fn -> :nothing end
system_process = true # Ако е false този процес ще си чака
Process.flag(:trap_exit, system_process)
pid = spawn_link(action)
receive do
msg -> IO.inspect(msg)
end
# {:EXIT, <pid>, :normal}action = fn -> exit(:stuff) end
system_process = true # Ако е false този процес ще умре с ** (EXIT from <pid>) :stuff
Process.flag(:trap_exit, system_process)
pid = spawn_link(action)
receive do
msg -> IO.inspect(msg)
end
# {:EXIT, <pid>, :stuff}- Аналогично на случай 1:
action = fn -> exit(:normal) end
system_process = true # Ако е false този процес ще си чака
Process.flag(:trap_exit, system_process)
pid = spawn_link(action)
receive do
msg -> IO.inspect(msg)
end
# {:EXIT, <pid>, :normal}action = fn -> raise("Stuff") end
system_process = true # Ако е false този процес умира с
# [error] Process <pid> raised an exception ** (RuntimeError) Stuff
Process.flag(:trap_exit, system_process)
pid = spawn_link(action)
receive do
msg -> IO.inspect(msg)
end
# {:EXIT, <pid>,
# {%RuntimeError{message: "Stuff"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 668]}]}}action = fn -> throw("Stuff") end
system_process = true # Ако е false този процес умира с
# [error] Process <pid> raised an exception ** (ErlangError) erlang error: {:nocatch, "Stuff"}
Process.flag(:trap_exit, system_process)
pid = spawn_link(action)
receive do
msg -> IO.inspect(msg)
end
# {:EXIT, <pid>,
# {{:nocatch, "Stuff"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 668]}]}}- Може да се използва да ликвидираме процес от друг процес
- Ако извикаме с
:killможем да убием даже процеси, които trap-ват сигнали. - Малко повече на тема Process.exit
- Child спецификацията идва с опция за shutdown : когато Supervisor трябва да спре под-процес.
- По дефаулт, когато това се случи Process.exit(child, :shutdown) се праща на процеса и се чака 5 секунди за чистене на ресурси.
- Ако процесът не хваща сигнали само това ще го убие, но ако хваща след тези 5 минути Process.exit(child, :kill) се праща.
- Тези пет секунди могат да се променят с число (в ms) или на :infinity.
- Можем да сложим и стойността :brutal_kill, тогава направо се праща Process.exit(child, :kill).
- Process.exit(child, :shutdown) ще наката под-процесът да извика terminate/2 callback функцията.
- Изход с
IO.puts/2иIO.write/2
IO.puts("По подразбиране пишем на стандартния изход.")
IO.puts(:stdio, "Можем да го направим и така.")
IO.puts(:stderr, "Или да пишем в стандартния изход за грешки.")IO.write(:stderr, "Това е грешка!")- Първият аргумент на
putsиwriteможе да е атом или pid. Нарича сеdeviceи представлява друг процес. - Вторият аргумент се очаква да е нещо от тип chardata.
- Низ, да речем
"Далия". - Списък от codepoint-и, да речем
[83, 79, 0x53]или[?S, ?O, ?S]или'SOS'. - Списък от codepoint-и и низове -
[83, 79, 83, "mayday!"]. - Списък от chardata, тоест списък от нещата в горните три точки :
[[83], [79, ["dir", 78]]].
IO.chardata_to_string([1049, [1086, 1091], "!"])
#=> "Йоу!"- Връща каквото му е подадено. Може да се
chain-ва. - Приема pretty print опции.
- Приема етикети.
- Чудесно за debugging.
defmodule TaskEnum do
def map(enumerable, fun) do
enumerable
|> IO.inspect(label: "Input", structs: false)
|> Enum.map(& Task.async(fn -> fun.(&1) end))
|> IO.inspect(label: "Tasks", width: 120, limit: :infinity)
|> Enum.map(& Task.await(&1))
end
end- Вход с IO.read/2, IO.gets/2, IO.getn/2 и IO.getn/3
IO.read(:line)
Хей, Хей<enter>
#=> "Хей, Хей\n"IO.gets("Кажи нещо!\n")
Кажи нещо!
Нещо!<enter>
#=> "Нещо!\n"- IO Server Time
- Функции като
writeиreadимат версии наречениbinwriteиbinread. - Разликата е, че приемат
iodata, вместоchardata. - По бързи са. Добри за четене на binary/не-unicode файлове.
- Подобно на chardata, iodata може да се дефинира като списък.
- За разлика от chardata, iodata списъкът е от цели числа които представляват байтове (0 - 255),
- binary с елементи със size, кратен на 8 (могат да превъртат) и такива списъци.
- Писане без конкатениране? Повече тук
IO.iodata_length([1, 2 | <<3, 4>>])
#=> 4IO.iodata_to_binary([1, << 2 >>, [[3], 4]])
#=> <<1, 2, 3, 4>>{:ok, file} = File.open("test.txt", [:write])
#=> {:ok, #PID<0.855.0>}
IO.binwrite(file, "some text!")
#=> :ok
File.close(file)
#=> :ok- Така наречения device всъщност е PID на процес или атом, който е ключ, сочещ към PID на процес.
- Когато отваряме файл се създава нов процес, който знае file descriptor-а на файла и управлява писането и четенето към и от него.
- Това означава, че всяка операция с файла минава през комуникация между процеси
- Именно за това има функции, които направо работят с файлове, като File.read/1, File.read!/1, File.write/3, File.write!/3.
{:ok, file} = File.open("program.txt", [:read])
#=> {:ok, #PID<0.82.0>}
IO.stream(file, :line)
|> Stream.map(fn line -> line <> "!" end)
|> Stream.each(fn line -> IO.puts line end)
|> Stream.run()- За повече четене тук
File.stream!(input_name, read_ahead: <buffer_size>)
|> Stream.<transform-or-filter>
|> Stream.into(File.stream!(output_name, [:delayed_write]))
|> Stream.runIO.puts [IO.ANSI.blue(), "text", IO.ANSI.reset()]{:ok, pid} = StringIO.open("data") #PID<0.136.0>}
StringIO.contents(pid) # {"data", ""}
IO.write(pid, "doom!") #:ok
StringIO.contents(pid) # {"data", "doom!"}
IO.read(pid, :line) # "data"
StringIO.contents(pid) # {"", "doom!"}
StringIO.close(pid) # {:ok, {"", "doom!"}}{:ok, file} = File.open("data", [:ram])
#=> {:ok, {:file_descriptor, :ram_file, #Port<0.1578>}}
IO.binread(file, :all)
#=> "data"IO.binread(file, :all)
#=> "":file.position(file, :bof)
IO.binread(file, :all)
#=> "data"Path.join("some", "path")
#=> "some/path"
Path.expand("~/development")
#=> "/home/meddle/development"










