litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 45 46 47 48 49 50 51 52 53 ... 75
Перейти на страницу:
а не как решение каждой проблемы, с которой вы сталкиваетесь.

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

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

1 ... 45 46 47 48 49 50 51 52 53 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?