learnxinyminutes-docs/ru/elixir.md
2024-12-08 23:20:53 -07:00

465 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
contributors:
- ["Joao Marques", "http://github.com/mrshankly"]
- ["Dzianis Dashkevich", "https://github.com/dskecse"]
- ["Ryan Plant", "https://github.com/ryanplant-au"]
translator:
- ["Ev Bogdanov", "https://github.com/evbogdanov"]
filename: learnelixir-ru.ex
---
Elixir — современный функциональный язык программирования, который работает на
виртуальной машине Erlang. Elixir полностью совместим с Erlang, но обладает
дружелюбным синтаксисом и предлагает больше возможностей.
```elixir
# Однострочные комментарии начинаются с символа решётки.
# Для многострочных комментариев отдельного синтаксиса нет,
# поэтому просто используйте несколько однострочных комментариев.
# Запустить интерактивную Elixir-консоль (аналог `irb` в Ruby) можно
# при помощи команды `iex`.
# Чтобы скомпилировать модуль, воспользуйтесь командой `elixirc`.
# Обе команды будут работать из терминала, если вы правильно установили Elixir.
## ---------------------------
## -- Базовые типы
## ---------------------------
# Числа
3 # целое число
0x1F # целое число
3.0 # число с плавающей запятой
# Атомы, которые являются нечисловыми константами. Они начинаются с символа `:`.
:hello # атом
# Кортежи, которые хранятся в памяти последовательно.
{1,2,3} # кортеж
# Получить доступ к элементу кортежа мы можем с помощью функции `elem`:
elem({1, 2, 3}, 0) #=> 1
# Списки, которые реализованы как связные списки.
[1,2,3] # список
# У каждого непустого списка есть голова (первый элемент списка)
# и хвост (все остальные элементы списка):
[head | tail] = [1,2,3]
head #=> 1
tail #=> [2,3]
# В Elixir, как и в Erlang, знак `=` служит для сопоставления с образцом,
# а не для операции присваивания.
#
# Это означает, что выражение слева от знака `=` (образец) сопоставляется с
# выражением справа.
#
# Сопоставление с образцом позволило нам получить голову и хвост списка
# в примере выше.
# Если выражения слева и справа от знака `=` не удаётся сопоставить, будет
# брошена ошибка. Например, если кортежи разных размеров.
{a, b, c} = {1, 2} #=> ** (MatchError)
# Бинарные данные
<<1,2,3>>
# Вы столкнётесь с двумя видами строк:
"hello" # Elixir-строка (заключена в двойные кавычки)
'hello' # Erlang-строка (заключена в одинарные кавычки)
# Все строки представлены в кодировке UTF-8:
"привет" #=> "привет"
# Многострочный текст
"""
Я текст на несколько
строк.
"""
#=> "Я текст на несколько\nстрок.\n"
# Чем Elixir-строки отличаются от Erlang-строк? Elixir-строки являются бинарными
# данными.
<<?a, ?b, ?c>> #=> "abc"
# Erlang-строка — это на самом деле список.
[?a, ?b, ?c] #=> 'abc'
# Оператор `?` возвращает целое число, соответствующее данному символу.
?a #=> 97
# Для объединения бинарных данных (и Elixir-строк) используйте `<>`
<<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>
"hello " <> "world" #=> "hello world"
# Для объединения списков (и Erlang-строк) используйте `++`
[1,2,3] ++ [4,5] #=> [1,2,3,4,5]
'hello ' ++ 'world' #=> 'hello world'
# Диапазоны записываются как `начало..конец` (оба включительно)
1..10 #=> 1..10
# Сопоставление с образцом применимо и для диапазонов:
lower..upper = 1..10
[lower, upper] #=> [1, 10]
# Карты (известны вам по другим языкам как ассоциативные массивы, словари, хэши)
genders = %{"david" => "male", "gillian" => "female"}
genders["david"] #=> "male"
# Для карт, где ключами выступают атомы, доступен специальный синтаксис
genders = %{david: "male", gillian: "female"}
genders.gillian #=> "female"
## ---------------------------
## -- Операторы
## ---------------------------
# Математические операции
1 + 1 #=> 2
10 - 5 #=> 5
5 * 2 #=> 10
10 / 2 #=> 5.0
# В Elixir оператор `/` всегда возвращает число с плавающей запятой.
# Для целочисленного деления применяйте `div`
div(10, 2) #=> 5
# Для получения остатка от деления к вашим услугам `rem`
rem(10, 3) #=> 1
# Булевые операторы: `or`, `and`, `not`.
# В качестве первого аргумента эти операторы ожидают булевое значение.
true and true #=> true
false or true #=> true
1 and true #=> ** (BadBooleanError)
# Elixir также предоставляет `||`, `&&` и `!`, которые принимают аргументы
# любого типа. Всё, кроме `false` и `nil`, считается `true`.
1 || true #=> 1
false && 1 #=> false
nil && 20 #=> nil
!true #=> false
# Операторы сравнения: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<`, `>`
1 == 1 #=> true
1 != 1 #=> false
1 < 2 #=> true
# Операторы `===` и `!==` более строгие. Разница заметна, когда мы сравниваем
# числа целые и с плавающей запятой:
1 == 1.0 #=> true
1 === 1.0 #=> false
# Elixir позволяет сравнивать значения разных типов:
1 < :hello #=> true
# При сравнении разных типов руководствуйтесь следующим правилом:
# число < атом < ссылка < функция < порт < процесс < кортеж < список < строка
## ---------------------------
## -- Порядок выполнения
## ---------------------------
# Условный оператор `if`
if false do
"Вы этого никогда не увидите"
else
"Вы увидите это"
end
# Противоположный ему условный оператор `unless`
unless true do
"Вы этого никогда не увидите"
else
"Вы увидите это"
end
# Помните сопоставление с образцом?
# Многие конструкции в Elixir построены вокруг него.
# `case` позволяет сравнить выражение с несколькими образцами:
case {:one, :two} do
{:four, :five} ->
"Этот образец не совпадёт"
{:one, x} ->
"Этот образец совпадёт и присвоит переменной `x` значение `:two`"
_ ->
"Этот образец совпадёт с чем угодно"
end
# Символ `_` называется анонимной переменной. Используйте `_` для значений,
# которые в текущем выражении вас не интересуют. Например, вам интересна лишь
# голова списка, а хвост вы желаете проигнорировать:
[head | _] = [1,2,3]
head #=> 1
# Для лучшей читаемости вы можете написать:
[head | _tail] = [:a, :b, :c]
head #=> :a
# `cond` позволяет проверить сразу несколько условий за раз.
# Используйте `cond` вместо множественных операторов `if`.
cond do
1 + 1 == 3 ->
"Вы меня никогда не увидите"
2 * 5 == 12 ->
"И меня"
1 + 2 == 3 ->
"Вы увидите меня"
end
# Обычно последним условием идёт `true`, которое выполнится, если все предыдущие
# условия оказались ложны.
cond do
1 + 1 == 3 ->
"Вы меня никогда не увидите"
2 * 5 == 12 ->
"И меня"
true ->
"Вы увидите меня (по сути, это `else`)"
end
# Обработка ошибок происходит в блоках `try/catch`.
# Elixir также поддерживает блок `after`, который выполнится в любом случае.
try do
throw(:hello)
catch
message -> "Поймана ошибка с сообщением #{message}."
after
IO.puts("Я выполнюсь всегда")
end
#=> Я выполнюсь всегда
# "Поймана ошибка с сообщением hello."
## ---------------------------
## -- Модули и функции
## ---------------------------
# Анонимные функции (обратите внимание на точку при вызове функции)
square = fn(x) -> x * x end
square.(5) #=> 25
# Анонимные функции принимают клозы и гарды.
#
# Клозы (от англ. clause) — варианты исполнения функции.
#
# Гарды (от англ. guard) — охранные выражения, уточняющие сопоставление с
# образцом в функциях. Гарды следуют после ключевого слова `when`.
f = fn
x, y when x > 0 -> x + y
x, y -> x * y
end
f.(1, 3) #=> 4
f.(-1, 3) #=> -3
# В Elixir много встроенных функций.
# Они доступны в текущей области видимости.
is_number(10) #=> true
is_list("hello") #=> false
elem({1,2,3}, 0) #=> 1
# Вы можете объединить несколько функций в модуль. Внутри модуля используйте `def`,
# чтобы определить свои функции.
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
# Чтобы скомпилировать модуль Math, сохраните его в файле `math.ex`
# и наберите в терминале: `elixirc math.ex`
defmodule PrivateMath do
# Публичные функции начинаются с `def` и доступны из других модулей.
def sum(a, b) do
do_sum(a, b)
end
# Приватные функции начинаются с `defp` и доступны только внутри своего модуля.
defp do_sum(a, b) do
a + b
end
end
PrivateMath.sum(1, 2) #=> 3
PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)
# Функции внутри модуля тоже принимают клозы и гарды
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)
# Из-за неизменяемых переменных в Elixir важную роль играет рекурсия
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
# Модули в Elixir поддерживают атрибуты.
# Атрибуты бывают как встроенные, так и ваши собственные.
defmodule MyMod do
@moduledoc """
Это встроенный атрибут
"""
@my_data 100 # А это ваш атрибут
IO.inspect(@my_data) #=> 100
end
# Одна из фишек языка — оператор `|>`
# Он передаёт выражение слева в качестве первого аргумента функции справа:
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]
## ---------------------------
## -- Структуры и исключения
## ---------------------------
# Структуры — это расширения поверх карт, привносящие в Elixir значения по
# умолчанию, проверки на этапе компиляции и полиморфизм.
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"}
# Доступ к полю структуры
joe_info.name #=> "Joe"
# Обновление поля структуры
older_joe_info = %{ joe_info | age: 31 }
#=> %Person{age: 31, height: 180, name: "Joe"}
# Блок `try` с ключевым словом `rescue` используется для обработки исключений
try do
raise "какая-то ошибка"
rescue
RuntimeError -> "перехвачена ошибка рантайма"
_error -> "перехват любой другой ошибки"
end
#=> "перехвачена ошибка рантайма"
# У каждого исключения есть сообщение
try do
raise "какая-то ошибка"
rescue
x in [RuntimeError] ->
x.message
end
#=> "какая-то ошибка"
## ---------------------------
## -- Параллелизм
## ---------------------------
# Параллелизм в Elixir построен на модели акторов. Для написания
# параллельной программы нам понадобятся три вещи:
# 1. Создание процессов
# 2. Отправка сообщений
# 3. Приём сообщений
# Новый процесс создаётся функцией `spawn`, которая принимает функцию
# в качестве аргумента.
f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>
spawn(f) #=> #PID<0.40.0>
# `spawn` возвращает идентификатор процесса (англ. process identifier, PID).
# Вы можете использовать PID для отправки сообщений этому процессу. Сообщения
# отправляются через оператор `send`. А для приёма сообщений используется
# механизм `receive`:
# Блок `receive do` ждёт сообщений и обработает их, как только получит. Блок
# `receive do` обработает лишь одно полученное сообщение. Чтобы обработать
# несколько сообщений, функция, содержащая блок `receive do`, должна рекурсивно
# вызывать себя.
defmodule Geometry do
def area_loop do
receive do
{:rectangle, w, h} ->
IO.puts("Площадь = #{w * h}")
area_loop()
{:circle, r} ->
IO.puts("Площадь = #{3.14 * r * r}")
area_loop()
end
end
end
# Скомпилируйте модуль и создайте процесс
pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0>
# Альтернативно
pid = spawn(Geometry, :area_loop, [])
# Отправьте сообщение процессу
send pid, {:rectangle, 2, 3}
#=> Площадь = 6
# {:rectangle,2,3}
send pid, {:circle, 2}
#=> Площадь = 12.56
# {:circle,2}
# Кстати, интерактивная консоль — это тоже процесс.
# Чтобы узнать текущий PID, воспользуйтесь встроенной функцией `self`
self() #=> #PID<0.27.0>
## ---------------------------
## -- Агенты
## ---------------------------
# Агент — это процесс, который следит за некоторым изменяющимся значением.
# Создайте агента через `Agent.start_link`, передав ему функцию.
# Начальным состоянием агента будет значение, которое эта функция возвращает.
{ok, my_agent} = Agent.start_link(fn -> ["красный", "зелёный"] end)
# `Agent.get` принимает имя агента и анонимную функцию `fn`, которой будет
# передано текущее состояние агента. В результате вы получите то, что вернёт
# анонимная функция.
Agent.get(my_agent, fn colors -> colors end) #=> ["красный", "зелёный"]
# Похожим образом вы можете обновить состояние агента
Agent.update(my_agent, fn colors -> ["синий" | colors] end)
```
## Ссылки
* [Официальный сайт](http://elixir-lang.org)
* [Шпаргалка по языку](http://media.pragprog.com/titles/elixir/ElixirCheat.pdf)
* [Книга "Programming Elixir"](https://pragprog.com/book/elixir/programming-elixir)
* [Книга "Learn You Some Erlang for Great Good!"](http://learnyousomeerlang.com/)
* [Книга "Programming Erlang: Software for a Concurrent World"](https://pragprog.com/book/jaerlang2/programming-erlang)