Макросы позволяют добавлять в язык новые команды, как в предыдущем упражнении:
defmodule Solution do
defmacro my_unless(condition, do: expression) do
quote do
if(!unquote(condition), do: unquote(expression))
end
end
end
На самом деле, в Elixir все конструкции имеют внутреннее представление, даже функции. Это означает, что с помощью макроса можно сгенерировать функцию:
defmodule Exercise do
defmacro create_multiplier(fn_name, factor) do
quote do
def unquote(fn_name)(value) do
unquote(factor) * value
end
end
end
end
Чтобы воспользоваться этим макросом, нужно создать другой модуль, так как определение функции доступно только внутри модуля:
defmodule MyModule do
require Exercise
Exercise.create_multiplier(:double, 2)
Exercise.create_multiplier(:triple, 3)
def run_example() do
x = double(2)
IO.puts("Two times 2 is #{x}")
end
end
MyModule.double(5)
# => 10
MyModule.triple(3)
# => 9
MyModule.run_example()
# => Two times 2 is 4
Создадим универсальный макрос, который за один вызов создает нужное количество функций:
defmodule Exercise do
defmacro create_functions(fn_list) do
Enum.map(fn_list, fn {name, factor} ->
quote do
def unquote(:"#{name}_value")(value) do
unquote(factor) * value
end
end
end)
end
end
И теперь опробуем макрос в новом модуле:
defmodule Example do
require Exercise
Exercise.create_functions([{:double, 2}, {:triple, 3}, {:nullify, 0}])
end
Example.double_value(2)
# => 4
Example.triple_value(2)
# => 6
Example.nullify_value(2)
# => 0
Если нужно создать макрос только внутри модуля, то defmacrop
то что нужно. Как и приватные функции, макрос объявленный таким образом, будет доступен только в модуле, где макрос объявлен и только во время компиляции.
Создайте макрос prohibit_words
, генерирующий функцию forbidden?
, в который передается список запрещенных слов и проверяется, запрещено ли слово, переданное в функцию forbidden?
. Если передано не слово, то функция возвращает false
:
defmodule Exercise
require Solution
Solution.prohibit_words(["hello", "world", "foo"])
end
Exercise.forbidden?("hello")
# => true
Exercise.forbidden?("test")
# => false
Exercise.forbidden?(1)
# => false
Exercise.forbidden?(%{hello: :world})
# => false
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1defmodule Test do
2 use ExUnit.Case
3
4 defmodule Exercise do
5 require Solution
6
7 Solution.prohibit_words(["hello", "world", "foo"])
8 end
9
10 test "prohibit_words work" do
11 assert Exercise.forbidden?("hello")
12 assert Exercise.forbidden?("world")
13 assert Exercise.forbidden?("foo")
14 refute Exercise.forbidden?("baz")
15 refute Exercise.forbidden?(2)
16 refute Exercise.forbidden?(%{hello: :world})
17 end
18end
19
Решение учителя откроется через: