Основная причина, по которой #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
В конце макросы расширяются и определяют четыре метода. Ключевым моментом, на который следует обратить внимание в этом примере, является использование \{{
. Обратная косая черта экранирует выражение синтаксиса макроса, поэтому оно не оценивается внешним макросом, что означает, что оно расширяется только внутренним макросом. На переменные макроса из внешнего макроса по-прежнему можно ссылаться во внутреннем макросе, используя переменную во внутреннем макросе,