diff --git a/modules/10-basics/70-interop/description.ru.yml b/modules/10-basics/70-interop/description.ru.yml index 6a39d04..1242f8e 100644 --- a/modules/10-basics/70-interop/description.ru.yml +++ b/modules/10-basics/70-interop/description.ru.yml @@ -67,15 +67,15 @@ theory: | # функция подсчитывающее количество слов из erlang :string.words("Hello World") # => ** (FunctionClauseError) no function clause matching in :string.strip_left/2 - # => + # => # => The following arguments were given to :string.strip_left/2: - # => + # => # => # 1 # => "Hello World" - # => + # => # => # 2 # => 32 - # => + # => # => (stdlib) string.erl:1661: :string.strip_left/2 # => (stdlib) string.erl:1659: :string.strip/3 # => (stdlib) string.erl:1597: :string.words/2 diff --git a/modules/10-basics/description.en.yml b/modules/10-basics/description.en.yml index 995b35c..7bbe1bb 100644 --- a/modules/10-basics/description.en.yml +++ b/modules/10-basics/description.en.yml @@ -1,4 +1,5 @@ --- name: Elixir Basics description: | + The Elixir language is quite complex. Most of its elements are more complex than they first appear. In the first module, we will look at modules and functions, basic data types and operations with them. diff --git a/modules/20-data-types/30-maps/description.ru.yml b/modules/20-data-types/30-maps/description.ru.yml index 1a48a26..e23153d 100644 --- a/modules/20-data-types/30-maps/description.ru.yml +++ b/modules/20-data-types/30-maps/description.ru.yml @@ -82,7 +82,6 @@ theory: | Map.delete(other_map, :a) # %{b: 2} ``` - instructions: | Реализуйте функцию `keys_sum`, которая принимает словарь и два ключа, извлекает значения по этим ключам, и возвращает сумму значений. Если ключа в словаре нет, то соответствующее значение не учитывается. diff --git a/modules/20-data-types/description.en.yml b/modules/20-data-types/description.en.yml index 758a89f..18822fd 100644 --- a/modules/20-data-types/description.en.yml +++ b/modules/20-data-types/description.en.yml @@ -1,4 +1,5 @@ --- name: Elixir data types description: | + In the second module we will continue to get acquainted with different data types: atoms, tuples, lists and dictionaries. If you are not familiar with functional programming, even at this stage, some of it may already seem strange. But don't be frightened, it's not hard to use all of this. diff --git a/modules/30-flow/15-pattern-matching-for-maps/description.ru.yml b/modules/30-flow/15-pattern-matching-for-maps/description.ru.yml index ff266ff..c5ed81b 100644 --- a/modules/30-flow/15-pattern-matching-for-maps/description.ru.yml +++ b/modules/30-flow/15-pattern-matching-for-maps/description.ru.yml @@ -7,16 +7,18 @@ theory: | ```elixir my_map = %{a: 1, b: 2, c: 3} - %{a: value} = my_map + %{a: value} = my_map + IO.puts(value) # => 1 ``` Если ключи не являются атомами, то синтаксис отличается: - + ```elixir my_map = %{"a" => 1, "b" => 2, "c" => 3} %{"a" => value1} = my_map IO.puts(value1) # => 1 + %{"b" => value2, "c" => value3} = my_map IO.puts(value2) # => 2 IO.puts(value3) # => 3 @@ -32,10 +34,11 @@ theory: | ``` Переменные можно использовать для извлечения значений, но не для извлечения ключей: - + ```elixir %{"c" => my_var} = my_map IO.puts(my_var) # => 3 + %{my_var => 1} = my_map # ** (CompileError) iex:17: cannot use variable my_var as map key inside a pattern. ``` @@ -48,9 +51,9 @@ theory: | ``` _pin_ operator извлекает текущее значение переменной и подставляет его в шаблон. И дальше это значение в шаблоне работает как литерал. - + _pin_ operator можно использовать и для ключа, и для значения: - + ```elixir value1 = 1 %{"a" => ^value1} = my_map @@ -67,18 +70,21 @@ instructions: | Обе функции генерируют исключение `MatchError` если в словаре нет нужных ключей. ```elixir - defmodule Solution do + Solution.get_values(%{a: 1, b: 2}) + # => {1, 2} + Solution.get_values(%{a: :ok, b: 42, c: true}) + # => {:ok, 42} - def get_values(data) do - # TODO реализация - end + Solution.get_values(%{}) + # => MatchError - def get_value_by_key(data, key) do - # TODO реализация - end + Solution.get_value_by_key(%{answer: 42}, :answer) + # => 42 + Solution.get_value_by_key(%{question: "6 * 7"}, :question) + # => "6 * 7" - end + Solution.get_value_by_key(%{a: 1}, :b) + # => MatchError ``` - tips: [] diff --git a/modules/30-flow/20-case/description.ru.yml b/modules/30-flow/20-case/description.ru.yml index 702929b..faf1129 100644 --- a/modules/30-flow/20-case/description.ru.yml +++ b/modules/30-flow/20-case/description.ru.yml @@ -6,18 +6,18 @@ theory: | Условные переходы в функциональных языках отличаются от императивных, потому что основаны на сопоставлении с образцом. Основная идея в том, что некое значение по очереди сравнивается с несколькими шаблонами, и в зависимости от того, с каким шаблоном оно совпадет, выполняется та или иная ветка кода. Есть несколько вариантов условных переходов: - - конструкция case; - - конструкция cond; + - конструкция `case`; + - конструкция `cond`; - тело функции (function clause); - - обработка исключений (rescue, catch); - - чтение сообщений из mailbox (receive). + - обработка исключений `rescue`, `catch`; + - чтение сообщений из mailbox `receive`. - Все они, кроме *cond*, реализуют эту идею. + Все они, кроме *cond*, реализуют эту идею. ## Рассмотрим конструкцию case. Для примера рассмотрим вычисление наибольшего общего делителя: - + ```elixir def gcd(a, b) do case rem(a, b) do @@ -26,24 +26,24 @@ theory: | end end ``` - + Здесь вычисляется значение `rem(a, b)` и сравнивается с двумя шаблонами. Первый шаблон -- литерал `0`. Если значение совпадает с ним, то выполняется код, завершающий рекурсию и возвращающий `b`. Второй шаблон -- переменная `c`. С этим шаблоном совпадут любые значения, и тогда выполняется вызов `gcd(b, c)`. Второй пример: - + ```elixir case Map.fetch(acc, word) do {:ok, count} -> Map.put(acc, word, count + 1) :error -> Map.put(acc, word, 1) end ``` - + Здесь выполняется вызов функции `Map.fetch(acc, word)`. Получившееся значение сравнивается с двумя шаблонами и выполняется соответствующий код. Шаблонов может быть несколько. И важен их порядок, потому что первый совпавший шаблон останавливает перебор оставшихся шаблонов. Если не совпал ни один из шаблонов, то генерируется исключение. В общем виде конструкция *case* выглядит так: - + ```elixir case Expr do Pattern1 [when GuardSequence1] -> @@ -53,11 +53,11 @@ theory: | BodyN end ``` - + Что такое GuardSequence -- цепочка охранных выражений, мы рассмотрим позже. case могут быть вложенными друг в друга: - + ```elixir def handle(animal, action) do case animal do @@ -76,7 +76,7 @@ theory: | ``` Вложенный даже на два уровня код плохо читается. Обычно этого можно избежать. Данный пример можно реализовать без вложенного case таким образом: - + ```elixir def handle(animal, action) do case {animal, action} do @@ -90,7 +90,7 @@ theory: | ## Охранные выражения (Guards) - Теперь вернемся к упомянутым выше охранным выражениям. + Теперь вернемся к упомянутым выше охранным выражениям. Не всегда достаточно шаблона, чтобы проверить все условия для ветвления в коде. Например, шаблоном нельзя проверить попадание числа в определенный диапазон. @@ -118,31 +118,31 @@ theory: | ```elixir when is_map(a) and map_size(a) > 10 -> ``` - + можно сразу писать: - + ```elixir when map_size(a) > 10 -> ``` instructions: | - + Реализовать функцию `join_game(user)`, которая принимает игрока в виде кортежа `{:user, name, age, role}` и определяет, разрешено ли данному игроку подключиться к игре. Если игроку уже исполнилось 18 лет, то он может войти в игру. Если роль игрока `:admin` или `:moderator`, то он может войти в игру независимо от возраста. Функция должна вернуть `:ok` или `:error`. Реализовать функцию `move_allowed?(current_color, figure)` которая определяет, разрешено ли данной шахматной фигуре сделать ход. Параметр `current_color` может быть либо `:white` либо `:black`, и он указывает, фигурам какого цвета разрешено сделать ход. Параметр `figure` представлен кортежем `{type, color}`, где `type` может быть один из: `:pawn`, `:rock`, `:bishop`, `:knight`, `:queen`, `:king`, а color может быть `:white` или `:black`. Фигура может сделать ход если её тип `:pawn` или `:rock` и её цвет совпадает с `current_color`. Функция должна вернуть `true` или `false`. - - ```elixir - defmodule Solution do - - def join_game(user) do - # TODO реализация - end - def move_allowed?(current_color, figure) do - # TODO реализация - end - - end + ```elixir + Solution.join_game({:user, "Bob", 17, :admin}) + # => :ok + Solution.join_game({:user, "Bob", 17, :moderator}) + # => :ok + Solution.join_game({:user, "Bob", 17, :member}) + # => :error + + Solution.move_allowed?(:white, {:pawn, :white}) + # => true + Solution.move_allowed?(:black, {:pawn, :white}) + # => false ``` tips: [] diff --git a/modules/30-flow/30-cond/description.ru.yml b/modules/30-flow/30-cond/description.ru.yml index ac8e59c..28efbac 100644 --- a/modules/30-flow/30-cond/description.ru.yml +++ b/modules/30-flow/30-cond/description.ru.yml @@ -9,11 +9,11 @@ theory: | ```elixir case Expr do - Pattern1 [when GuardSequence1] -> - Body1 - ... - PatternN [when GuardSequenceN] -> - BodyN + Pattern1 [when GuardSequence1] -> + Body1 + ... + PatternN [when GuardSequenceN] -> + BodyN end ``` @@ -21,11 +21,11 @@ theory: | ```elixir cond do - GuardSequence1 -> - Body1 - ... - GuardSequenceN -> - BodyN + GuardSequence1 -> + Body1 + ... + GuardSequenceN -> + BodyN end ``` @@ -50,12 +50,12 @@ theory: | num > 5 -> IO.puts("more than 5") end end + my_fun(20) # => more than 10 my_fun(8) # => more than 5 my_fun(3) # ** (CondClauseError) no cond clause evaluated to a truthy value ``` - ## Конструкция if В Эликсир есть привычная всем конструкция *if*: @@ -87,23 +87,26 @@ theory: | Есть важное отличие от императивных языков -- в функциональных языках *if* всегда возвращает какое-то значение. ```elixir - > a = 5 - > b = 10 - > c = if a > b do - ...> a - ...> else - ...> b - ...> end - > IO.puts(c) # 10 + a = 5 + b = 10 + c = if a > b do + a + else + b + end + + IO.puts(c) + # => 10 ``` Некоторые функциональные языки требуют, чтобы часть *else* всегда присутствовала, потому что значение нужно вернуть в любом случае, выполняется условие *if* или не выполняется. Эликсир этого не требует: ```elixir - > c = if a > b do - ...> a - ...> end - > IO.puts(c) # nil + c = if a > b do + a + end + + IO.puts(c) # => nil ``` instructions: | @@ -113,17 +116,21 @@ instructions: | Реализовать функцию `double_win?(a_win, b_win, c_win)`, которая принимает 3 булевых параметра для трех игроков. Если победили игроки A и B, то функция возвращает атом `:ab`. Если победили игроки A и C, то функция возвращает атом `:ac`, если победили игроки B и C, то функция возвращает атом `:bc`. Во всех остальных случаях функция возвращает `false`. ```elixir - defmodule Solution do - - def single_win?(a_win, b_win) do - # TODO реализация - end - - def double_win?(a_win, b_win, c_win) do - # TODO реализация - end - - end + Solution.single_win?(true, false) + # => true + Solution.single_win?(false, true) + # => true + Solution.single_win?(true, true) + # => false + + Solution.double_win?(true, true, false) + # => :ab + Solution.double_win?(true, false, true) + # => :ac + Solution.double_win?(true, true, true) + # => false + Solution.double_win?(true, false, false) + # => false ``` tips: [] diff --git a/modules/30-flow/40-function-clause/description.ru.yml b/modules/30-flow/40-function-clause/description.ru.yml index 891575d..6ce4051 100644 --- a/modules/30-flow/40-function-clause/description.ru.yml +++ b/modules/30-flow/40-function-clause/description.ru.yml @@ -11,12 +11,15 @@ theory: | def handle({:dog, name}, :add) do IO.puts("add dog #{name}") end + def handle({:dog, name}, :remove) do IO.puts("remove dog #{name}") end + def handle({:cat, name}, :add) do IO.puts("add cat #{name}") end + def handle({:cat, name}, :remove) do IO.puts("remove cat #{name}") end @@ -30,6 +33,7 @@ theory: | def handle(animal, action) do IO.puts("do something") end + def handle({:dog, name}, :add) do IO.puts("add dog #{name}") end @@ -49,12 +53,15 @@ theory: | def handle({:dog, name, age}) when age > 10 do IO.puts("#{name} is a dog older than 10") end + def handle({:dog, name, _age}) do IO.puts("#{name} is a 10 years old or younger dog") end + def handle({:cat, name, age}) when age > 10 do IO.puts("#{name} is a cat older than 10") end + def handle({:cat, name, _age}) do IO.puts("#{name} is a 10 years old or younger cat") end @@ -62,7 +69,6 @@ theory: | Конструкция *case* и тела функций полностью эквивалентны друг другу. Выбор того или иного варианта является личным предпочтением разработчика. - instructions: | Поиграем в "крестики-нолики". Игровая доска размером 3х3 ячеек представлена кортежем из 3-х кортежей: @@ -85,16 +91,22 @@ instructions: | Реализовать функцию `check_who_win(state)`, которая получает состояние и возвращает победителя, если он есть. Функция должна определить наличие трех крестиков или трех ноликов по горизонтали, или по вертикали, или по диагонали. В случае победы крестиков функция возвращает `{:win, :x}`, в случае победы ноликов функция возвращает `{:win, :o}`, если победы нет, функция возвращает `:no_win`. ```elixir - defmodule Solution do - - def valid_game?(state) do - # TODO реализация - end - - def check_who_win(state) do - # TODO реализация - end - end + Solution.valid_game?({{:x, :x, :x}, {:x, :x, :x}, {:x, :x, :x}}) + # => true + Solution.valid_game?({{:f, :f, :f}, {:f, :f, :f}, {:f, :f, :f}}) + # => true + + Solution.valid_game?({{:x, :o, :some}, {:f, :x, :o}, {:o, :o, :x}}) + # => false + Solution.valid_game?({{:x, :o, :f}, {:f, :x, :x, :o}, {:o, :o, :x}}) + # => false + + Solution.check_who_win({{:x, :x, :x}, {:f, :f, :o}, {:f, :f, :o}}) + # => {:win, :x} + Solution.check_who_win({{:o, :x, :f}, {:o, :f, :x}, {:o, :f, :f}}) + # => {:win, :o} + Solution.check_who_win({{:x, :f, :f}, {:f, :x, :x}, {:f, :f, :o}}) + # => :no_win ``` tips: [] diff --git a/modules/30-flow/description.en.yml b/modules/30-flow/description.en.yml index bc3763c..f3407dd 100644 --- a/modules/30-flow/description.en.yml +++ b/modules/30-flow/description.en.yml @@ -1,4 +1,5 @@ --- name: Flow control constructions description: | + In development diff --git a/modules/35-fp/10-recursion/description.ru.yml b/modules/35-fp/10-recursion/description.ru.yml index 22d4936..b6edd29 100644 --- a/modules/35-fp/10-recursion/description.ru.yml +++ b/modules/35-fp/10-recursion/description.ru.yml @@ -5,7 +5,6 @@ theory: | В Эликсир, как и во всех функциональных языках, нет циклов. Их заменяет рекурсия. - ## Пример 1 Начнем сразу с практики, рассмотрим пример функции, которая вычисляет длину списка: @@ -44,7 +43,6 @@ theory: | 1 + 1 + 1 + 1 + 1 + 0 ``` - ## Пример 2 Возьмем пример немного сложнее, реализуем функцию, которая возвращает максимальный элемент в списке. @@ -106,4 +104,6 @@ instructions: | Solution.range(3, 2) # [] ``` -tips: [] +tips: + - | + [Статья в Hexlet про рекурсию](https://ru.hexlet.io/blog/posts/recursive) diff --git a/modules/35-fp/20-recursion-with-acc/description.ru.yml b/modules/35-fp/20-recursion-with-acc/description.ru.yml index 3492c91..b069401 100644 --- a/modules/35-fp/20-recursion-with-acc/description.ru.yml +++ b/modules/35-fp/20-recursion-with-acc/description.ru.yml @@ -29,24 +29,26 @@ theory: | Применим рекурсивную функцию с аккумулятором: ```elixir - def filter_adults(users), do: filter_adults(users, []) - - defp filter_adults([], acc), do: Enum.reverse(acc) - - defp filter_adults([user | users], acc) do - {:user, _, _, age} = user - if age > 16 do - filter_adults(users, [user | acc]) - else - filter_adults(users, acc) + defmodule Solution do + def filter_adults(users), do: filter_adults(users, []) + + defp filter_adults([], acc), do: Enum.reverse(acc) + + defp filter_adults([user | users], acc) do + {:user, _, _, age} = user + if age > 16 do + filter_adults(users, [user | acc]) + else + filter_adults(users, acc) + end end end - > Solition.filter_adults(users) - [ - {:user, 1, "Bob", 23}, - {:user, 2, "Helen", 20} - ] + Solition.filter_adults(users) + # => [ + # => {:user, 1, "Bob", 23}, + # => {:user, 2, "Helen", 20} + # => ] ``` Функция получает на вход два аргумента: список пользователей, и ещё один список, где будет накапливаться результат выполнения -- аккумулятор. В начале выполнения список пользователей полный, а аккумулятор пустой. На каждом шаге итерации мы будем брать один элемент из первого списка, и, если он нам нужен, то класть его в аккумулятор. В итоге первый список опустошится, а аккумулятор наполнится. @@ -71,17 +73,19 @@ theory: | Реализация: ```elixir - def get_id_name(users), do: get_id_name(users, []) + defmodule Solution do + def get_id_name(users), do: get_id_name(users, []) - defp get_id_name([], acc), do: Enum.reverse(acc) + defp get_id_name([], acc), do: Enum.reverse(acc) - defp get_id_name([user | users], acc) do - {:user, id, name, _} = user - get_id_name(users, [{id, name} | acc]) + defp get_id_name([user | users], acc) do + {:user, id, name, _} = user + get_id_name(users, [{id, name} | acc]) + end end - > Solution.get_id_name(users) - [{1, "Bob"}, {2, "Helen"}, {3, "Bill"}, {4, "Kate"}] + Solution.get_id_name(users) + # => [{1, "Bob"}, {2, "Helen"}, {3, "Bill"}, {4, "Kate"}] ``` Опять два тела у функции. Первое тело отвечает за завершение рекурсии и возврат результата, накопленного в аккумуляторе. @@ -96,24 +100,26 @@ theory: | И вот пример со сложным аккумулятором. Допустим нам нужно разделить пользователей на два списка: несовершеннолетние и взрослые. ```elixir - def split_teens_and_adults(users), do: split_teens_and_adults(users, {[], []}) + defmodule Solution do + def split_teens_and_adults(users), do: split_teens_and_adults(users, {[], []}) - defp split_teens_and_adults([], {teens, adults}) do - {Enum.reverse(teens), Enum.reverse(adults)} - end + defp split_teens_and_adults([], {teens, adults}) do + {Enum.reverse(teens), Enum.reverse(adults)} + end - defp split_teens_and_adults([user | users], {teens, adults}) do - {:user, _, _, age} = user - if age > 16 do - split_teens_and_adults(users, {teens, [user | adults]}) - else - split_teens_and_adults(users, {[user | teens], adults}) + defp split_teens_and_adults([user | users], {teens, adults}) do + {:user, _, _, age} = user + if age > 16 do + split_teens_and_adults(users, {teens, [user | adults]}) + else + split_teens_and_adults(users, {[user | teens], adults}) + end end end - > Solution.split_teens_and_adults(users) - {[{:user, 3, "Bill", 15}, {:user, 4, "Kate", 14}], - [{:user, 1, "Bob", 23}, {:user, 2, "Helen", 20}]} + Solution.split_teens_and_adults(users) + # => {[{:user, 3, "Bill", 15}, {:user, 4, "Kate", 14}], + # => [{:user, 1, "Bob", 23}, {:user, 2, "Helen", 20}]} ``` В этот раз наш аккумулятор -- это кортеж из двух списков. В первом теле функции, как обычно, возвращаем результат. И тут мы разворачиваем оба списка. Во втором теле функции обрабатываем каждый элемент списка, извлекаем и проверяем возраст. Тех, кто моложе 16 лет, кладем в первый список в аккумуляторе, остальных во второй список. @@ -130,9 +136,12 @@ instructions: | {:user, 3, "Bill", 15}, {:user, 4, "Kate", 14} ] + Solution.filter_by_age(users, 16) # [{:user, 1, "Bob", 23}, {:user, 2, "Helen", 20}] Solution.filter_by_age(users, 14) # [{:user, 1, "Bob", 23}, {:user, 2, "Helen", 20}, {:user, 3, "Bill", 15}] Solution.filter_by_age(users, 22) # [{:user, 1, "Bob", 23}] ``` -tips: [] +tips: + - | + [Статья в Hexlet про рекурсию](https://ru.hexlet.io/blog/posts/recursive) diff --git a/modules/35-fp/30-immutability/description.ru.yml b/modules/35-fp/30-immutability/description.ru.yml index c2ca96d..839ff9b 100644 --- a/modules/35-fp/30-immutability/description.ru.yml +++ b/modules/35-fp/30-immutability/description.ru.yml @@ -50,7 +50,7 @@ theory: | # => %{a: 42, b: 500} yet_another_map = Map.put(my_map, :c, 100500) # => %{a: 42, c: 100500} - + my_map # => %{a: 42} other_map @@ -58,7 +58,7 @@ theory: | yet_another_map # => %{a: 42, c: 100500} ``` - + instructions: | Реализуем [шифр Цезаря](https://ru.wikipedia.org/wiki/%D0%A8%D0%B8%D1%84%D1%80_%D0%A6%D0%B5%D0%B7%D0%B0%D1%80%D1%8F) -- простой способ шифрования путем сдвига каждого символа на константу. diff --git a/modules/35-fp/40-anonymous-fn/description.ru.yml b/modules/35-fp/40-anonymous-fn/description.ru.yml index f91c383..eb3e8b5 100644 --- a/modules/35-fp/40-anonymous-fn/description.ru.yml +++ b/modules/35-fp/40-anonymous-fn/description.ru.yml @@ -29,7 +29,7 @@ theory: | double = &(&1 * &1) more_magic.(double, 5) # => 25 ``` - + При использовании сокращенного синтаксиса для анонимных функций следует быть аккуратным, так как при большом количестве аргументов можно легко запутаться с их порядком, особенно, если в функции есть сложная логика. instructions: | diff --git a/modules/35-fp/description.en.yml b/modules/35-fp/description.en.yml index 8add2df..67fc942 100644 --- a/modules/35-fp/description.en.yml +++ b/modules/35-fp/description.en.yml @@ -1,6 +1,7 @@ --- name: Functional Programming Basics description: | + Elixir is a bright representative of the family of functional languages. It's time to learn what functional programming is, what elements it consists of, and how it differs from other programming paradigms. We have already been introduced to one of the most important elements of FP - pattern matching. Now let us consider the other elements: immutability, recursion, higher-order functions, and anonymous functions. diff --git a/modules/35-fp/description.ru.yml b/modules/35-fp/description.ru.yml index 5dae98a..9292f7d 100644 --- a/modules/35-fp/description.ru.yml +++ b/modules/35-fp/description.ru.yml @@ -5,4 +5,3 @@ description: | Эликсир — яркий представитель семейства функциональных языков. Пришло время узнать, что такое функциональное программирование, из каких элементов оно состоит, и чем отличается от других парадигм программирования. Мы уже познакомились с одним из важнейших элементов ФП — сопоставлением с образцом. Теперь рассмотрим другие элементы: иммутабельность, рекурсию, функции высшего порядка и анонимные функции. - diff --git a/modules/40-collections/10-map/description.ru.yml b/modules/40-collections/10-map/description.ru.yml index 95bf24b..1e5d177 100644 --- a/modules/40-collections/10-map/description.ru.yml +++ b/modules/40-collections/10-map/description.ru.yml @@ -7,7 +7,7 @@ theory: | ```elixir numbers = [1, 2, 3, 4] - + doubled_numbers = Enum.map(numbers, fn (x) -> x * 2 end) IO.puts(doubled_numbers) # => [2, 4, 6, 8] @@ -25,7 +25,7 @@ theory: | ```elixir numbers = [1, 2, 3] - + incremented_numbers = Enum.map(numbers, fn (x) -> x + 1 end) IO.puts(numbers) # => [1, 2, 3] @@ -40,7 +40,7 @@ theory: | ```elixir numbers = [1, 2, 3, 4] - result = + result = numbers |> Enum.map(fn (x) -> x + 1 end) |> Enum.map(fn (x) -> x * 5 end) @@ -56,15 +56,15 @@ instructions: | ```elixir Solution.zip([], []) - ; => [] + # => [] Solution.zip([1, 2, 3, 4], [5, 6, 7, 8]) - ; => [[1, 5], [2, 6], [3, 7], [4, 8]] + # => [[1, 5], [2, 6], [3, 7], [4, 8]] Solution.zip([1, 2], [3, 4]) - ; => [[1, 3], [2, 4]] + # => [[1, 3], [2, 4]] Solution.zip([1, 2], [3]) - ; => [[1, 3], [2, nil]] + # => [[1, 3], [2, nil]] Solution.zip([1], [3, 4]) - ; => [[1, 3], [nil, 4]] + # => [[1, 3], [nil, 4]] ``` tips: diff --git a/modules/40-collections/20-filter/description.ru.yml b/modules/40-collections/20-filter/description.ru.yml index 66120df..34b7f2e 100644 --- a/modules/40-collections/20-filter/description.ru.yml +++ b/modules/40-collections/20-filter/description.ru.yml @@ -7,11 +7,11 @@ theory: | ```elixir numbers = [12, 2, 30, 4, 55, 10, 11] - + result = Enum.filter(numbers, fn (x) -> x >= 10 end) IO.puts(result) # => [12, 30, 55, 10, 11] - result = + result = numbers |> Enum.filter(fn (x) -> x < 20 end) |> Enum.filter(fn (x) -> rem(x, 2) == 0 end) @@ -32,11 +32,11 @@ instructions: | ```elixir Solution.inc_numbers(["foo", false, ["foo"]]) - ; => [] + # => [] Solution.inc_numbers([10, "foo", false, true, ["foo"], 1.2, %{}, 32]) - ; => [11, 2.2, 33] + # => [11, 2.2, 33] Solution.inc_numbers([1, 2, 3, 4, 5, 6.0]) - ; => [2, 3, 4, 5, 6, 7.0] + # => [2, 3, 4, 5, 6, 7.0] ``` tips: diff --git a/modules/40-collections/30-reduce/description.ru.yml b/modules/40-collections/30-reduce/description.ru.yml index a6b14f0..656f4af 100644 --- a/modules/40-collections/30-reduce/description.ru.yml +++ b/modules/40-collections/30-reduce/description.ru.yml @@ -30,7 +30,7 @@ theory: | Часто используют левую свёртку `reduce` из модуля `Enum` потому, что она более интуитивна — двигается от первого элемента к последнему. Однако, иногда полезна правая. - Если появилась необходимость использовать правую свертку, то можно обратиться к модулю `List` и функции `foldr`, однако стоит понимать, что в общем случае лучше использовать `Enum`, так как модуль работает со всеми структурами, имплементирующими протокол `Enumerable`, в то время как `List` работает только со списками. Про протоколы будет рассказано чуть позже. + Если появилась необходимость использовать правую свертку, то можно обратиться к модулю `List` и функции `foldr`, однако стоит понимать, что в общем случае лучше использовать `Enum`, так как модуль работает со всеми структурами, имплементирующими протокол `Enumerable`, в то время как `List` работает только со списками. Про протоколы будет рассказано чуть позже. ```elixir List.foldr([1, 2, 3], 0, &(&1 + &2)) # => 6 @@ -41,22 +41,24 @@ theory: | ``` instructions: | + Реализуйте функцию `max_delta`, которая должна принимать два списка чисел и вычислять максимальную разницу (абсолютное значение разницы) между соответствующими парами элементов. Примеры: ```elixir Solution.max_delta([], []) - ; => 0 + # => 0 Solution.max_delta([10, -15, 35], [2, -12, 42]) - ; => 8 + # => 8 Solution.max_delta([-5], [-15]) - ; => 10 + # => 10 ``` + Вам пригодятся функции `abs` и `max`: ```elixir - abs(42) ; 42 - abs(-13) ; 13 - max(1, 5) ; 5 + abs(42) # => 42 + abs(-13) # => 13 + max(1, 5) # => 5 ``` tips: diff --git a/modules/40-collections/40-comprehension/description.ru.yml b/modules/40-collections/40-comprehension/description.ru.yml index 6917c41..1b66080 100644 --- a/modules/40-collections/40-comprehension/description.ru.yml +++ b/modules/40-collections/40-comprehension/description.ru.yml @@ -60,7 +60,7 @@ theory: | instructions: | - Создайте функцию `fetch_gamers`, которая принимает список сотрудников и выводит список активных сотрудников (статус `:active`) сотрудников у которых хобби связаны с играми (тип хобби `:gaming`). Структура сотрудников описана в примере: + Создайте функцию `fetch_gamers`, которая принимает список сотрудников и выводит список активных сотрудников (статус `:active`) сотрудников у которых хобби связаны с играми (тип хобби `:gaming`). Структура сотрудников описана в примере: ```elixir employees = [ @@ -86,10 +86,10 @@ instructions: | Solution.fetch_gamers(employees) - # [ - # {"Eric", %{name: "Text Adventures", type: :gaming}}, - # {"Greg", %{name: "Dungeons & Dragons", type: :gaming}} - # ] + # => [ + # => {"Eric", %{name: "Text Adventures", type: :gaming}}, + # => {"Greg", %{name: "Dungeons & Dragons", type: :gaming}} + # => ] ``` tips: diff --git a/modules/40-collections/50-stream/description.ru.yml b/modules/40-collections/50-stream/description.ru.yml index 807fa01..ae6fd18 100644 --- a/modules/40-collections/50-stream/description.ru.yml +++ b/modules/40-collections/50-stream/description.ru.yml @@ -25,8 +25,8 @@ theory: | numbers = [1,2,3] numbers - |> Stream.map(&(&1 * &1)) - |> Stream.zip([:a, :b, :c]) + |> Stream.map(&(&1 * &1)) + |> Stream.zip([:a, :b, :c]) |> Stream.filter(fn({n, s}) -> n > 2 and s != :c end) |> Enum.to_list # => [{4, :b}] @@ -38,8 +38,8 @@ theory: | numbers = [1, 2, 3] numbers - |> Stream.map(&(&1 * &1)) - |> Stream.zip([:a, :b, :c]) + |> Stream.map(&(&1 * &1)) + |> Stream.zip([:a, :b, :c]) |> Stream.filter(fn({n, s}) -> n > 2 and s != :c end) # => #Stream<[ # => enum: #Function<73.29975488/2 in Stream.zip_with/2>, @@ -64,19 +64,19 @@ theory: | Возьмем еще один пример. Допустим у нас есть большой файл, который содержит всю текстовую часть статей из Википедии, который весит примерно 20 Гб и мы хотим в нем найти самое длинное слово. Реализуем через `Enum`: ```elixir - File.read!("data/wiki.txt") - |> String.split("\n") + File.read!("data/wiki.txt") + |> String.split("\n") |> Enum.map(fn(line) -> String.split(line, " ") end) |> Enum.map(fn(words) -> ... |> Enum.max_by ``` - В этом случае мы загрузим в оперативную память весь файл, который весит 20 Гб, так еще и пройдемся по нему 4 раза! - + В этом случае мы загрузим в оперативную память весь файл, который весит 20 Гб, так еще и пройдемся по нему 4 раза! + Теперь воспользуемся `Stream`: ```elixir - File.stream!("data/wiki.txt") + File.stream!("data/wiki.txt") |> Stream.map(fn(line) -> String.split(line, " ") end) |> Stream.map(fn(words) -> ... |> Enum.max_by @@ -106,9 +106,9 @@ theory: | ``` `Stream.unfold` можно представить, как функцию, обратную *свертке* (fold или reduce). Если fold сворачивает список в одиночное значение, то unfold наоборот, из одиночного значения разворачивает список. - + Функция принимает на вход начальное значение и разворачивающую функцию. Разворачивающая функция принимает на вход текущее состояние, и возвращает кортеж из двух значений. Первый элемент кортежа, это то, что становится очередным элементом списка. Второй элемент кортежа, это новое состояние, которое передается в разворачивающую функцию на следующем шаге. - + Рассмотрим пример с числами Фибоначчи: ```elixir diff --git a/modules/40-collections/description.en.yml b/modules/40-collections/description.en.yml index 8deb7f5..ae412ac 100644 --- a/modules/40-collections/description.en.yml +++ b/modules/40-collections/description.en.yml @@ -1,4 +1,5 @@ --- name: Collections description: | + In this module, we will study typical ways of working with lists and other collections: map, filter, and reduce. We will get acquainted with the Enum module, the workhorse for all collections. We will also study list constructors (lists comprehension) and streams. diff --git a/modules/50-errors/10-errors-handling/description.ru.yml b/modules/50-errors/10-errors-handling/description.ru.yml index 1783087..270c7e0 100644 --- a/modules/50-errors/10-errors-handling/description.ru.yml +++ b/modules/50-errors/10-errors-handling/description.ru.yml @@ -33,14 +33,14 @@ theory: | ```elixir try do raise "boom!" - rescue + rescue e in RuntimeError -> e end # => %RuntimeError{message: "boom!"} try do raise "boom!" - rescue + rescue RuntimeError -> "oops" end # => "oops" @@ -77,7 +77,7 @@ theory: | # => (iex 1.15.0) lib/iex/evaluator.ex:331: IEx.Evaluator.eval_and_inspect/3 ``` - По сути, исключения в Elixir используются по прямому назначению, они выбрасываются только когда произошло действительно что-то, чего в нормальной ситуации произойти не должно. В большинстве же остальных языков программирования исключения используются как еще один способ управлять потоком выполнения программы. Почти оператор `GOTO`, только замаскированный. + По сути, исключения в Elixir используются по прямому назначению, они выбрасываются только когда произошло действительно что-то, чего в нормальной ситуации произойти не должно. В большинстве же остальных языков программирования исключения используются как еще один способ управлять потоком выполнения программы. Почти оператор `GOTO`, только замаскированный. Иногда нужно подчистить какой-то ресурс, при возникновении исключения, тогда подойдет `after`: diff --git a/modules/50-errors/description.en.yml b/modules/50-errors/description.en.yml index 6cb9c78..f85906f 100644 --- a/modules/50-errors/description.en.yml +++ b/modules/50-errors/description.en.yml @@ -1,4 +1,5 @@ --- name: Errors handling description: | + In this module we will study error and exception handling in Elixir. diff --git a/modules/55-processes/description.en.yml b/modules/55-processes/description.en.yml index a2487be..c00b9ef 100644 --- a/modules/55-processes/description.en.yml +++ b/modules/55-processes/description.en.yml @@ -1,4 +1,5 @@ --- name: Processes description: | + In Elixir, all code runs inside processes. Processes are isolated from each other, run in parallel, and communicate via messaging. Processes are not the only basis for parallel operation, but they provide the basis for building distributed and fault-tolerant programs. In this module, we will look at the processes and actor model that underlie the language. diff --git a/modules/60-macros/10-macros-intro/description.ru.yml b/modules/60-macros/10-macros-intro/description.ru.yml index 778e3ff..bf37376 100644 --- a/modules/60-macros/10-macros-intro/description.ru.yml +++ b/modules/60-macros/10-macros-intro/description.ru.yml @@ -60,7 +60,7 @@ theory: | Макрос работает, но выглядит неудобно, в следующем упражнении рассмотрим как сделать макрос более читаемым. - Интересный факт: создатель языка, Жозе Валим, при добавлении макросов в Elixir, вдохновлялся Clojure. + Интересный факт: создатель языка, Жозе Валим, при добавлении макросов в Elixir, вдохновлялся Clojure. instructions: | diff --git a/modules/60-macros/30-new-functionality/description.ru.yml b/modules/60-macros/30-new-functionality/description.ru.yml index ce70c7d..a5f7c53 100644 --- a/modules/60-macros/30-new-functionality/description.ru.yml +++ b/modules/60-macros/30-new-functionality/description.ru.yml @@ -8,8 +8,8 @@ theory: | defmodule Solution do defmacro my_unless(condition, do: expression) do quote do - if(!unquote(condition), do: unquote(expression)) - end + if(!unquote(condition), do: unquote(expression)) + end end end ``` diff --git a/modules/60-macros/40-macros-hygiene/description.ru.yml b/modules/60-macros/40-macros-hygiene/description.ru.yml index f5cc2dd..ae165e1 100644 --- a/modules/60-macros/40-macros-hygiene/description.ru.yml +++ b/modules/60-macros/40-macros-hygiene/description.ru.yml @@ -35,7 +35,7 @@ theory: | end end - Sample.quoted() + Sample.quoted() #=> {:x, [line: 3], Sample} ``` @@ -90,7 +90,7 @@ theory: | # => [3, 4, 5] ``` - Обратите внимание, что передается вторым аргументом в `var`. Это контекст объявления переменной, благодаря которому пересечения переменных не произойдет, если они были объявлены ранее. + Обратите внимание, что передается вторым аргументом в `var`. Это контекст объявления переменной, благодаря которому пересечения переменных не произойдет, если они были объявлены ранее. Есть магическая структура `__ENV__`, которая хранит всю информацию о *скомпилированном* окружении, включая модули, файлы, переменные, импорты и так далее: @@ -131,7 +131,7 @@ instructions: | # => Execution result is: 6 # => 6 - + Exercise.run_fn(fn -> %{hello: :world} end) # => Started execution... # => Execution result is: %{hello: :world} diff --git a/modules/60-macros/50-macros-ast/description.ru.yml b/modules/60-macros/50-macros-ast/description.ru.yml index 0ebaafa..e0a7768 100644 --- a/modules/60-macros/50-macros-ast/description.ru.yml +++ b/modules/60-macros/50-macros-ast/description.ru.yml @@ -49,8 +49,8 @@ theory: | - Макросы сложнее отлаживать; - Макросы притягивают макросы, то есть приходится писать дополнительные обвязки-макросы. - Перед тем как написать, задумайтесь, есть ли возможность решить задачу с помощью функции? Ответ на этот вопрос почти всегда будет - `да`. - + Перед тем как написать, задумайтесь, есть ли возможность решить задачу с помощью функции? Ответ на этот вопрос почти всегда будет - `да`. + Хоть Elixir помогает и одновременно ограничивает разработчиков, например: - Макросы по умолчанию гигиеничны; - Макросы не дают возможности глобально внедрять произвольный код, то есть необходимо напрямую в *конкретном* модуле вызвать `require` или `import` нужного макроса; @@ -68,7 +68,7 @@ instructions: | ```elixir new_module = """ defmodule MyModule do - + end """ diff --git a/modules/60-macros/description.en.yml b/modules/60-macros/description.en.yml index 42bd5d8..4e8e860 100644 --- a/modules/60-macros/description.en.yml +++ b/modules/60-macros/description.en.yml @@ -1,4 +1,5 @@ --- name: Macros in Elixir description: | + In this module we will look at metaprogramming in the Elixir language. Thanks to macros, you can greatly extend the expressiveness of the language by creating DSLs, but the use of macros is black magic, so they must be used with care and understanding.