summaryrefslogtreecommitdiffhomepage
path: root/ru-ru/elixir-ru.html.markdown
blob: d1e7725c9a5e5b10317f6602b073a4f056dfc6e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
---
language: Elixir
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
lang: ru-ru
---

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)