В Elixir есть функции, упрощающих работу с абстрактным синтаксическим деревом (АСТ) языка. Например:
module = """
defmodule Example do
def sum(a, b) do
a + b
end
end
"""
{:ok, result} = Code.string_to_quoted(module)
result
# => {:defmodule, [line: 1],
# => [
# => {:__aliases__, [line: 1], [:Example]},
# => [
# => do: {:def, [line: 2],
# => [
# => {:sum, [line: 2], [{:a, [line: 2], nil}, {:b, [line: 2], nil}]},
# => [do: {:+, [line: 3], [{:a, [line: 3], nil}, {:b, [line: 3], nil}]}]
# => ]}
# => ]
# => ]}
quoted = quote do: 1 + 2
quoted |> Macro.to_string |> IO.puts
# => 1 + 2
# => :ok
quoted_pipe = quote do: 100 |> div(10) |> div(5)
Macro.unpipe(quoted_pipe)
# => [
# => {100, 0},
# => {{:div, [context: Elixir, imports: [{2, Kernel}]], [10]}, 0},
# => {{:div, [context: Elixir, imports: [{2, Kernel}]], [5]}, 0}
# => ]
Теперь подведем итоги модуля. Мы рассмотрели, как с помощью макросов можно расширять возможности языка и напрямую работать с АСТ.
Однако, в большинстве случаев прибегать к макросам является плохой затеей:
- Макросы сложнее понимать;
- Макросы сложнее отлаживать;
- Макросы притягивают макросы, то есть приходится писать дополнительные обвязки-макросы.
Перед тем как написать, задумайтесь, есть ли возможность решить задачу с помощью функции? Ответ на этот вопрос почти всегда будет - да
.
Хоть Elixir помогает и одновременно ограничивает разработчиков, например:
- Макросы по умолчанию гигиеничны;
- Макросы не дают возможности глобально внедрять произвольный код, то есть необходимо напрямую в конкретном модуле вызвать require
или import
нужного макроса;
- Использование макросов в коде явно, поэтому неожиданного поведения, как например, полного переопределения функции, за спиной у разработчика не происходит;
- Использование явных quote
и unqoute
тоже упрощает понимание макроса, так как сразу видно, что будет выполнено сразу, а что потом.
Но даже эти механизмы не снимают ответственность с разработчика за написанный им код, пишите макросы ответственно и вдумчиво, и только если в этом есть острая необходимость!
Создайте функцию collect_module_stats
, которая принимает строку, в которой определяется модуль и функции модуля, а затем подсчитывается статистика по функциям, которые определены.
Для начала, изучите функцию string_to_quoted
модуля Code
и функцию prewalk
из модуля Macro
. Формат собираемой статистики представлен в примерах:
new_module = """
defmodule MyModule do
end
"""
Solution.collect_module_stats(new_module)
# => []
new_module = """
defmodule MyModule do
def hello() do
"world"
end
end
"""
Solution.collect_module_stats(new_module)
# => [%{arity: 0, name: :hello}]
new_module = """
defmodule MyModule do
def hello() do
"world"
end
defp test(a, b) do
a + b
end
end
"""
Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 0, name: :hello}]
new_module = """
defmodule MyModule do
def hello(string) do
[string, "world"]
end
def magic(a, b, c) do
(a + b) * c
end
defp test(a, b) do
a + b
end
end
"""
Solution.collect_module_stats(new_module)
# => [%{arity: 2, name: :test}, %{arity: 3, name: :magic}, %{arity: 1, name: :hello}]
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.