Иногда возникает необходимость расширить сам язык, создав предметно-ориентированный язык (DSL), либо реализовать какие-то новые возможности. Elixir с помощью макросов позволяет достичь этого, а сам процесс написания макросов называется метапрограммированием.
Макросы похожи на функции, но действуют совершенно иначе, чем функции. Объявляется макрос с помощью инструкции defmacro
. Для начала рассмотрим отличия макросов и функций:
# объявим модуль с функцией и макросом
defmodule Exercise do
def my_fn(x) do
IO.inspect(x)
x
end
defmacro my_macro(x) do
IO.inspect(x)
x
end
end
Чтобы воспользоваться макросом, нужно подключить модуль с помощью инструкции require
в котором макрос объявлен:
require Exercise
Exercise.my_fn(1 + 2)
# => 3
# => 3
Exercise.my_macro(1 + 2)
# => {:+, [line: 12], [1, 2]}
# => 3
Вызов функции понятен, Elixir вычисляет значение выражения 1 + 2
, затем передает его дальше в функцию, где полученное значение выводится на экран и возвращается из функции. В случае с макросом, вместо вычисления выражения, оно интерпретируется как кортеж, затем этот кортеж выводится на экран и возвращается из макроса, а после интерпретатор Elixir вычисляет возвращенный кортеж.
Рассмотрим внимательнее кортеж, который оказался в макросе {:+, [line: 12], [1, 2]}
, где :+
- оператор, [line: 12]
- метаданные об операции, [1, 2]
- список операндов. Теперь, обладая пониманием, что означает этот кортеж, напишем макрос, который удваивает переданный аргумент:
defmodule Exercise do
defmacro double(x) do
{:*, [], [2, x]}
end
end
require Exercise
Exercise.double(2)
# => 4
Exercise.double(10)
# => 20
Exercise.double(2 * 2)
# => 8
Exercise.double(2 + 1 - 5)
# => -4
Макрос работает, но выглядит неудобно, в следующем упражнении рассмотрим как сделать макрос более читаемым.
Интересный факт: создатель языка, Жозе Валим, при добавлении макросов в Elixir, вдохновлялся Clojure.
Создайте макрос my_abs
, который берет абсолютное значение переданного аргумента (поможет функция abs
):
require Solution
Solution.my_abs(-2)
# => 2
Solution.my_abs(2)
# => 2
Solution.my_abs(-5 * 100)
# => 500
Solution.my_abs(-2 - 100 + 1)
# => 101
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1defmodule Test do
2 use ExUnit.Case
3
4 require Solution
5
6 test "my_abs macro work" do
7 assert Solution.my_abs(-2) == 2
8 assert Solution.my_abs(2) == 2
9 assert Solution.my_abs(-5 * 100) == 500
10 assert Solution.my_abs(-2 - 100 + 1) == 101
11 end
12end
13
Решение учителя откроется через: