В 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}]
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1defmodule Test do
2 use ExUnit.Case
3
4 describe "collect_module_stats work" do
5 test "when no function defined" do
6 new_module = """
7 defmacro MyModule do
8 require Integer
9
10 @some_attr "1"
11
12 defmacro no_interference do
13 quote do: a = 1
14 end
15 end
16 """
17
18 assert Solution.collect_module_stats(new_module) == []
19 end
20
21 test "when few functions defined" do
22 new_module = """
23 defmacro MyModule do
24 def hello() do
25 "world"
26 end
27
28 def test(a, b) do
29 a + b
30 end
31 end
32 """
33
34 assert Solution.collect_module_stats(new_module) == [
35 %{arity: 2, name: :test},
36 %{arity: 0, name: :hello}
37 ]
38 end
39
40 test "when few functions and protocols defined" do
41 new_module = """
42 defmacro MyModule do
43 def hello(string) do
44 [string, "world"]
45 end
46
47 def magic(a, b, c) do
48 (a + b) * c
49 end
50
51 defp test(a, b) do
52 a + b
53 end
54 end
55 """
56
57 assert Solution.collect_module_stats(new_module) == [
58 %{arity: 2, name: :test},
59 %{arity: 3, name: :magic},
60 %{arity: 1, name: :hello}
61 ]
62 end
63 end
64end
65
Решение учителя откроется через: