Выбрать главу

Макросами легко злоупотреблять из-за предоставляемых ими возможностей. Однако, как говорится: с большой силой приходит и большая ответственность. Эмпирическое правило заключается в том, что если вы можете добиться того, чего хотите, с помощью обычного метода, используйте обычный метод и используйте макросы как можно реже. Это не значит, что макросов следует избегать любой ценой, а скорее, что их следует использовать стратегически, а не как решение каждой проблемы, с которой вы сталкиваетесь.

Макрос можно определить с помощью ключевого слова макроса:

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 можно использовать внутри макроса, но он должен использоваться в синтаксисе макроса и не может использоваться для генерации кода. Лучше всего это продемонстрировать на примере: