theme | style | paginate | footer | backgroundImage | marp |
---|---|---|---|---|---|
uncover |
.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;
}
|
true |
Курс по Elixir 2023, ФМИ |
linear-gradient(to bottom, #E0EAFC, #CFDEF3) |
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 error
raise 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!")
end
defmodule VeryBadError do
defexception message: "Лошо!!!"
end
try 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
end
try 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>>])
#=> 4
IO.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.run
IO.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"