| 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 |
- Какви основни типове знаем?
- Как да правим наши "типове"?
- Какво е протокол?
- Кои са четирите слоя на езика?
- Как да работим с двоичните структури - binaries
- Как са представени двоичните структури
- Низове
- Обхождане на низове
- Списък тип charlist
<<>>
<< 123, 23, 1 >>
<< 0b01111011, 0b00010111, 0b00000001 >> == << 123, 23, 1 >>
# true
<< 280 >>
# <<24>><< 280::size(16) >> # Същото като << 280::16 >>
# <<1, 24>>
<< 0b00000001, 0b00011000 >> == << 280::16 >>
<< 129::7 >>
# <<1::size(7)>>- Интересно е съхранението на числа с плаваща запетая като
binaries. - Винаги се представят като
binaryот 8 байта (double precision):
<< 5.5::float >>
# <<64, 22, 0, 0, 0, 0, 0, 0>><< 83, 79, 83 >> <> << 24, 4 >>
<< 83, 79, 83, << 24, 4 >> >>Kernel.byte_size(<< 83, 79, 83 >>)
byte_size(<< 34::5, 23::2, 12::2 >>)Kernel.bit_size(<< 34::5, 23::2, 12::2 >>)Kernel.is_bitstring/1- Винаги е истина за каквато и да е валидна поредица от данни между<<и>>. Няма значение дължината върната отKernel.bit_size/1.Kernel.is_binary/1- Истина е само акоKernel.bit_size/1връща число кратно на8- тоест структурата е поредица от байтове.
С функцията Kernel.binary_part/3 можем да боравим с части от дадена binary структура:
binary_part(<< 83, 79, 83, 23, 21, 12 >>, 1, 3)
# <<79, 83, 23>>
binary_part(<< 83, 79, 83, 23, 21, 12 >>, 2, -1)
binary_part(<< 83, 79, 83, 23, 21, 12 >>, 1, 6)
binary_part(<< 83, 79, 83, 23, 21, 12 >>, -1, 3)Функцията Kernel.binary_part/3 вътрешно ползва :erlang.binary_part/3, която е BIF, позволен в guard-ове:
case <<17, 13, 14, 15>> do
v when binary_part(v, 2, -2) == <<17, 13>> -> :ok
_ -> :nope
endОще една функция от Erlang : :erlang.split_binary/2, отново ползва :erlang.binary_part/3, но не може да е част от guard:
:erlang.split_binary(<<4, 65, 129>>, 2)
# {<<4, 65>>, <<129>>}Като всичко друго в Elixir, binaries също можем да съпоставяме:
<< x, y, x >> = << 83, 79, 83 >>
x
y<< x, y, z::binary >> = << 83, 79, 83, 43, 156 >>
z
# <<83, 43, 156>>
<< x, y, z >> = << 83, 79, 83, 43, 156 >>
<< x, y, z::bitstring >> = << 83, 79, 83, 43, 156 >><<
sign::size(1),
exponent::size(11),
mantissa::size(52)
>> = << 4815.162342::float >>
sign
# 0:math.pow(-1, sign) *
(1 + mantissa / :math.pow(2, 52)) *
:math.pow(2, exponent - 1023)- float
- binary или bytes
- integer модификатор, който се прилага по подразбиране
- bits или bitstrig
- utf8
- utf16
- utf32
- суфикси -little и -big за избор на endianness
<< x::5, y::bits >> = << 225 >>
x
y
<< 0b11111::5 >> = x
<< 0b111::3 >> = y- size задава дължина в битове, когато работим с integer.
- Aко работим с binary модификатор, size е в байтове.
<< x::binary-size(4), _::binary >> =
<< 83, 222, 0, 345, 143, 87 >>
x # 4 байта
# <<83, 222, 0, 89>>- Всеки процес в Elixir има собствен heap.
- Всеки процес съхранява различни структури от данни и стойности в този heap.
- Когато два процеса си комуникират, съобщенията се копират между heap-овете им.
- Ако binary-то е 64 байта или по-малко, то е съхранявано в heap-a на процеса си.
- Такива binary структурки наричаме heap binaries.
- Когато структурата ни е по-голяма от 64 байта, тя се пази в обща памет за всички процеси на даден
node. - В process heap-a се пази малко обектче - ProcBin.
- Binary структура може да бъде сочена от множество ProcBin указатели от множество процеси.
- Пази се reference counter, който брои всички тези указатели.
- Когато той стане 0, Garbage Collector-ът ще може да изчисти binary-то от общия heap.
- Такива binary структури наричаме refc binaries.
- Също се създават при конкатенация (след малко)
- Указател към съществуващо binary, сочещ към част от него.
- Няма копиране.
- Броячът на референции се увеличава.
- Това може да доведе до проблеми, когато имаме sub binary от много голямо binary, защото паметта няма да бъде освободена.
- Функцията binary_part създава sub binary.
- Подобен на sub binary, но оптимизиран за binary pattern matching.
- Държи указател към двоичните данни в паметта.
- Когато нещо е match-нато, указателят се придвижва напред.
- Компилаторът избягва създаването на sub binary (ако може).
- Ако е възможно се преизползва един и същ matching context.
- Повече на тази тема тук
x = << 83, 222, 0, 89 >> # Heap binary (< 64 bytes)
y = x <> << 225, 21 >> # Refc (concat -> 2*y size or 256 => 256 bytes)
z = y <> << 125, 156 >> # Refc, but reuses the memory
u = z <> << 2, 4 >> # Refc, but reuses the memory
a = y <> << 15, 16, 19 >> # Refc (concat -> 2*a size or 256)defmodule BPM do
def binary_to_list(<< head, tail::binary>>) do
[head | binary_to_list(tail)]
end
def binary_to_list(<<>>), do: []
end
BPM.binary_to_list("Let's there be list!")- Можем да реферираме стойности, които вече сме съпоставили.
str = String.duplicate("a", 56)
# "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
size = byte_size(str)
# 56
size_value_bin = <<size::32, str::binary>>
# <<0, 0, 0, 56, 97, 97, 97, ...>>
<<
payload_size::32,
payload::binary-size(payload_size)
>> = size_value_bin
# <<0, 0, 0, 56, 97, 97, 97, ...>>
payload_size
# 56
payload
# "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"- Erlang е създаден за решаване на проблеми в телеком индустрията
- Поради това, Erlang е особено подходящ за имплементация на binary протоколи
- В тази поредица от блогпостве може да видите какви са стъпките при имплементация на MySQL протокола в Elixir.
Низовете в Elixir използват unicode и се представят като binary - поредица от unicode codepoint-и в UTF-8 енкодинг.
0XXXXXXX
- При UTF-8 даден codepoint се състои от един до четири байта (може и в повече).
- Първите 128 codepoint-а съвпадат с ASCII codepoint-ите.
- Te се пазят в един байт, който започва с 0:
<< 0b00110000 >>
# "0"
<< 0b00110001 >>
# "1"
<< 0b00110010 >>
# "2"
<< 0b00110101 >>
# "5"
<< 0b00111001 >>
# "9"110XXXXX 10XXXXXX
- След като 128-възможности за 1 байт се изчерпат, започват да се ползват по 2 байта.
- В този шаблон за 2-байтови codepoint-и, виждаме че първият байт винаги започва с 110.
- Двете единици значат - 2 байта, байт започващ с 10, означава че е част от поредица от байтове.
?Ъ
#=> 1066
Integer.to_string(1066, 2)
# "10000101010"Префиксваме с 110 и допълваме до байт с битовете от числото (110)10000
Префиксваме с 10 и допълваме до байт с битовете, които останаха (10)101010
<< 0b11010000, 0b10101010 >>
"Ъ"Шаблонът за 3-байтови codepoint-и е:
1110XXXX 10XXXXXX 10XXXXXX
А шаблонът за 4-байтовите:
11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
Можем да заключим че има три типа байтове:
- Единичен - започва с 0 и представлява първите 128 ASCII-compatible codepoint-a.
- Байт-продължение - започва с 10, следва други байтове в codepoint от повече от 1 байт.
- Байт-начало - започва с 110, 1110, 11110, първи байт от серия байтове представляващи codepoint.
- Символите или графемите не винаги са точно един codepoint.
- Има графеми, които могат да се представят и като един codepoint и като няколко.
name = "Николай"
String.codepoints(name)
# ["Н", "и", "к", "о", "л", "а", "й"]
String.graphemes(name)
String.graphemes(name) == String.codepoints(name)name2 = "Николаи\u0306"
String.codepoints(name2)
#=> ["Н", "и", "к", "о", "л", "а", "и", ̆"<не може да се представи>"]
String.graphemes(name2)
# ["Н", "и", "к", "о", "л", "а", "й"]А ето как да пишем директно codepoint-и \u:
Integer.to_string(1066, 16) # Codepooint of "Ъ" in hex
# "42A"
"\u042Aгъл"
# Ако не са 4 hex цифри : \u{D...}Функции като String.reverse/1, работят правилно, защото ползват String.graphemes/1.
String.reverse("Николаи\u0306")
# Аналогично:
String.graphemes(name2) |> Enum.reverse |> Enum.join("")Kernel.bit_size "Николаи\u0306"
Kernel.byte_size "Николаи\u0306"
String.length "Николаи\u0306"String.at("Искам бира!", 6)
String.at("Искам бира!", -1)
String.at("Искам бира!", 12)<< _::binary-size(11), x::utf8, _::binary >> = "Искам бира!"
x
# 1073
<< x::utf8 >>
# б<< "Искам ", x::utf8, _::binary >> = "Искам бира!"String.next_codepoint("Николаи\u0306")
# {"Н", "иколай"}
String.next_codepoint("и\u0306")
String.next_grapheme("и\u0306")
# {"й", ""}
String.next_grapheme_size("и\u0306")
# {4, ""}
String.next_grapheme_size("\u0306")poem = """
Ще строим завод,
огромен завод,
със яки
бетонни стени!
Мъже и жени,
народ,
ще строим завод
за живота!
"""
large_factory = String.slice(poem, 18..30)
String.slice(poem, 18, 13)str =
(
Enum.to_list(?a..?z) ++
Enum.to_list(?A..?Z) ++
Enum.to_list(?а..?я) ++
Enum.to_list(?А..?Я) ++
[32, 46]
)
|> Stream.cycle()
|> Stream.take(2_000_000)
|> Enum.shuffle()
|> List.to_string()defmodule ACounterSlice do
def count_it(str) do
count(str, 0)
end
defp count("", n), do: n
defp count(str, n) do
the_next_n = next_n(String.starts_with?(str, "a"), n)
count(String.slice(str, 1..-1), the_next_n)
end
defp next_n(true, n), do: n + 1
defp next_n(false, n), do: n
endТова е около 3.18 секунди за низ с дължина над 2_000_000
{time, _result} = :timer.tc(ACounterSlice, :count_it, [str])
"#{time / 1000_000}s"defmodule ACounterNextGrapheme do
def count_it(str) do
count(str, 0)
end
defp count("", n), do: n
defp count(str, n) do
{next_grapheme, rest} = String.next_grapheme(str)
count(rest, next_n(next_grapheme == "a", n)
)
end
defp next_n(true, n), do: n + 1
defp next_n(false, n), do: n
endДобре същият отговор, но сега за 0.8 секунди
{time, _result} = :timer.tc(ACounterNextGrapheme, :count_it, [str])
"#{time / 1000_000}s"defmodule ACounterMatch do
def count_it(str) do
count(str, 0)
end
defp count("", n), do: n
defp count(<< c::utf8, rest::binary >>, n)
when c == ?a do
count(rest, n + 1)
end
defp count(<< _::utf8, rest::binary >>, n) do
count(rest, n)
end
endТази имплементация със същия низ е около 0.1 секунди.
{time, _result} = :timer.tc(ACounterMatch, :count_it, [str])
"#{time / 1000_000}s"- Списък от codepoint-и е точно това: списък от цели числа, които са codepoint-и.
- Те са интересно наследство от Erlang, практически няма да ги ползвате много, но някои Erlang библиотеки може и да имат функции, които ги приемат като аргументи.
- Използваме единични кавички за да ги създадем.
'Erlang'
[?E, ?r, ?l, ?a, ?n, ?g]
"Erlang" == 'Erlang'Списъкът се принтира в кавички, само ако codepoint-ите са в ASCII-range (0-127, и могат да се принтират).
'Ерланг'
is_list('Ерланг')
[69, 114, 108, 97, 110, 103]
[69, 114, 108, 97, 110, 103] ++ [33]
inspect([83, 79, 83], charlists: :as_lists)За да виждаме списъци от числа винаги като списъци от числа в IEX:
IEx.configure(inspect: [charlists: :as_lists])- Можем да преобразуваме charlist към низ с
to_string - Обратното става с
to_charlist









