Основная причина, по которой #each
#each
можно использовать внутри макроса, но он должен использоваться в синтаксисе макроса и не может использоваться для генерации кода. Лучше всего это продемонстрировать на примере:{% begin %}
{% hash = {"foo" => "bar", "biz" => "baz"} %}
{% for key, value in hash %}
puts "#{{{key}}}=#{{{value}}}"
{% end %}
{% end %}
{% begin %}
{% arr = [1, 2, 3] %}
{% hash = {} of Nil => Nil %}
{% arr.each { |v| hash[v] = v * 2 } %}
puts({{hash}})
{% end %}
В этом примере мы перебирали ключи и значения хеша, генерируя вызов метода puts
ArrayLiteral#each
для перебора каждого значения и установки вычисленного значения в хеш-литерал, который затем печатаем. В большинстве случаев синтаксис for in
можно использовать вместо #each
, но #each
нельзя использовать вместо for in
. Проще говоря, поскольку метод #each
использует блок, у него нет возможности вывод сгенерированного кода. Таким образом, его можно использовать только для итерации, а не генерации кода.Следующее, что делает наш макрос def_methods
if
, чтобы определить, должен ли он генерировать метод или нет для текущего числа. Операторы if/unless
в макросах работают идентично своим аналогам во время выполнения, хотя и в рамках синтаксиса макросов.Далее обратите внимание, что у этого метода есть комментарий, включающий {{idx}}
Наконец, у нас есть логика, создающая метод. В данном случае мы интерполировали индекс из цикла в строку, представляющую имя метода. Обратите внимание, что мы использовали для строки метод #id
#id
возвращает значение как MacroId
, что по существу нормализует значение как один и тот же идентификатор, независимо от типа входных данных. Например, вызов #id
для “foo”
, :foo
и foo
приводит к возврату того же значения foo
. Это полезно, поскольку позволяет вызывать макрос с любым идентификатором, который предпочитает пользователь, при этом создавая тот же базовый код.В самом конце определения макроса вы могли заметить строку {{debug}}
# Returns the number at index 0.
def number_0
1
end
# Returns the number at index 1.
def number_1
3
end
Поскольку макрос становится все более и более сложным, это может быть невероятно полезно для обеспечения того, чтобы он генерировал то, что должно быть.
Макрос также может генерировать другие макросы. Однако при этом необходимо соблюдать особую осторожность, чтобы гарантировать правильное экранирование выражений внутреннего макроса. Например, следующий макрос аналогичен предыдущему примеру, но вместо непосредственного определения методов он создает другой макрос и немедленно вызывает его, в результате чего создаются связанные методы:
macro def_macros(*numbers)
{% for num, idx in numbers %}
macro def_num_{{idx}}_methods(n)
def num_\{{n}}
\{{n}}
end
def num_\{{n}}_index
{{idx}}
end
end
def_num_{{idx}}_methods({{num}})
{% end %}
end
def_macros 2, 1
pp num_1_index # => 1
pp num_2_index # => 0
В конце макросы расширяются и определяют четыре метода. Ключевым моментом, на который следует обратить внимание в этом примере, является использование \{{