Запуск этой программы приведет к следующему выводу:
name
"George"
Главное, на что следует обратить внимание, — это вывод значения, когда оно находится в контексте макроса. Поскольку макросы принимают узлы AST, макрос Var
, который представляет локальную переменную или аргумент блока. Это можно подтвердить, добавив в макрос строку, состоящую из {{pp value.class_name}}
, которая в конечном итоге напечатает "Var”
. Мы узнаем больше об узлах AST позже в этой главе.
Макросами легко злоупотреблять из-за предоставляемых ими возможностей. Однако, как говорится:
Макрос можно определить с помощью ключевого слова макроса:
macro def_method(name)
def {{name.id}}
puts "Hi"
end
end
def_method foo
foo
В этом примере мы определили макрос с именем def_method
, который принимает один аргумент. В целом макросы очень похожи на обычные методы с точки зрения их определения, при этом основные различия заключаются в следующем:
• Аргументы макроса не могут иметь ограничений типа.
• Макросы не могут иметь ограничений по типу возвращаемого значения.
• Аргументы макроса не существуют во время выполнения, поэтому на них можно ссылаться только в синтаксисе макроса.
Макросы ведут себя аналогично методам класса в отношении их области действия. Макросы могут быть определены внутри типа и вызываться вне его, используя синтаксис метода класса. Аналогично, вызовы макросов будут искать определение в цепочке предков типа, например, в родительских типах или включенных модулях. Также можно определить частные макросы, которые сделают их видимыми в том же файле только в том случае, если они объявлены на верхнем уровне или только в пределах определенного типа, в котором они были объявлены.
Синтаксис макроса состоит из двух форм: {{ ... }} и {% ... %}
. Первый используется, когда вы хотите вывести какое-то значение в программу. Последний используется как часть потока управления макросом, например, циклы, условная логика, присвоение переменных и т. д. В предыдущем примере мы использовали синтаксис двойной фигурной скобки, чтобы вставить значение аргумента name в программу в качестве имени метода, которое в данном случае — foo
. Затем мы вызвали метод, в результате чего программа напечатала Hi
.
Макросы также могут расширяться до нескольких элементов и иметь более сложную логику для определения того, что будет сгенерировано. Например, давайте определим метод, который принимает переменное количество аргументов, и создадим метод для доступа к каждому значению, возможно, только для нечетных чисел:
macro def_methods(*numbers, only_odd = false)
{% for num, idx in numbers %}
{% if !only_odd || (num % 2) != 0 %}
# Returns the number at index {{idx}}.
def {{"number_#{idx}".id}}
{{num}}
end
{% end %}
{% end %}
{{debug}}
end
def_methods 1, 3, 6, only_odd: true
pp number_0
pp number_1
В этом примере происходит нечто большее, чем мы видим! Давайте разберемся. Сначала мы определили макрос под названием def_methods
, который принимает переменное количество аргументов с необязательным логическим флагом, которому по умолчанию присвоено значение false
. Макрос ожидает, что вы предоставите ему серию чисел, с помощью которых он создаст методы для доступа к числу, используя индекс каждого значения для создания уникального имени метода. Необязательный флаг заставит макрос создавать методы только для нечетных чисел, даже если в макрос также были переданы четные числа.
Цель использования аргументов splat
и именованных аргументов — показать, что макросы похожи на методы, которые могут быть написаны таким же образом. Однако разница становится более очевидной, когда вы попадаете в тело макроса. Обычно метод #each
используется для итерации коллекции. В случае макроса вы должны использовать синтаксис for item, index in collection
, который также можно использовать для итерации фиксированного количества раз или для перебора ключей/значений Hash/NamedTuple
через for i in (0.. 10)
, а для ключа — значение в hash_or_named_tuple
соответственно.