Шрифт:
Интервал:
Закладка:
Макрос можно определить с помощью ключевого слова макроса:
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 соответственно.
Основная причина, по которой #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
- QT 4: программирование GUI на С++ - Жасмин Бланшет - Программирование
- C# для профессионалов. Том II - Симон Робинсон - Программирование
- Как спроектировать современный сайт - Чои Вин - Программирование
- Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT. Часть 2 - Александр Фролов - Программирование
- Краткое введение в программирование на Bash - Гарольд Родригес - Программирование
- Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен - Программирование
- Каждому проекту своя методология - Алистэр Коуберн - Программирование
- Разработка ядра Linux - Роберт Лав - Программирование
- Графические интерфейсы пользователя Java - Тимур Сергеевич Машнин - Программирование
- C# 4.0: полное руководство - Герберт Шилдт - Программирование