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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 47 48 49 50 51 52 53 54 55 ... 75
Перейти на страницу:
компиляции значение константы будет установлено либо в значение переменной среды BUILD_SHA_HASH, либо в пустую строку, если она не была установлена (все это происходит во время компиляции):

COMMIT_SHA = {{ env("BUILD_SHA_HASH") || "" }}

pp COMMIT_SHA

При запуске этого кода обычно печатается пустая строка, а при установке связанной переменной env выводится это значение. Установка этого значения через переменную env, а не генерация внутри самого макроса с помощью системного вызова, гораздо более переносима, поскольку не зависит от Git, а также гораздо проще интегрируется с внешними системами сборки, такими как Make.

Одним из ограничений макросов является то, что сгенерированный из макроса код также должен быть действительным кодом Crystal, как показано здесь:

def {{"foo".id}}

  "foo"

end

Этот предыдущий код не является допустимой программой, поскольку метод неполный и не полностью определен в макросе. Этот метод можно включить в макрос, обернув все тегами {% begin %}/{% end %}, которые будут выглядеть следующим образом:

{% begin %}

  def {{"foo".id}}

    "foo"

  end

{% end %}

На этом этапе вы должны иметь четкое начальное представление о том, что такое макросы, как их определять и для каких случаев использования они предназначены, что позволит вам сохранить ваш код СУХИМ (DRY). Далее мы рассмотрим API макросов, чтобы можно было создавать более сложные макросы.

Понимание API макросов

В примерах из предыдущего раздела в контексте макроса использовались различные переменные разных типов, такие как числа, которые мы перебираем, строки, которые мы используем для создания идентификаторов, и логические значения, которые мы сравниваем для условной генерации кода. Было бы легко предположить, что это напрямую соответствует стандартным типам Number, String и Bool. Однако это не так. Как мы упоминали в разделе «Определение макросов» этой главы, макросы работают на узлах AST и, как таковые, имеют свой собственный набор типов, похожий на связанные с ними обычные типы Crystal, но с подмножеством API. Например, типы, с которыми мы до сих пор работали, включают NumberLiteral, StringLiteral и BoolLiteral.

Все типы макросов находятся в пространстве имен Crystal::Macros в документации API, которая находится по адресу https://crystal-lang.org/api/Crystal/Macros.html. К наиболее распространенным/полезным типам относятся следующие:

• Def: описывает определение метода.

• TypeNode: описывает тип (класс, структура, модуль, библиотека).

• MetaVar: описывает переменную экземпляра.

• Arg: описывает аргумент метода.

•Annotation: представляет аннотацию, применяемую к типу, методу или переменной экземпляра (подробнее об этом в следующей главе).

Crystal предоставляет удобный способ получить экземпляр первых двух типов в виде макропеременных @def и @type. Как следует из их названий, использование @def внутри метода вернет экземпляр Def, представляющий этот метод. Аналогично, использование @type вернет экземпляр TypeNode для связанного типа. Доступ к другим типам можно получить через методы, основанные на одном из этих двух типов. Например, запуск следующей программы выведет "Метод hello внутри Foo":

class Foo

  def hello

    {{"The #{@def.name} method within #{@type.name}"}}

  end

end

pp Foo.new.hello

Другой, более продвинутый способ получения TypeNode — использование макрометода parse_type. Этот метод принимает StringLiteral, который может быть создан динамически, и возвращает один из нескольких типов макросов в зависимости от того, что представляет собой строка. Дополнительную информацию см. в документации по методу https://crystal-lang.org/api/Crystal/Macros.html.

Как мы упоминали ранее, API макросов позволяет нам вызывать фиксированное подмножество обычных методов API для литеральных типов. Другими словами, это позволяет нам вызывать ArrayLiteral#select, но не ArrayLiteral#each_repeated_permutation, или StringLiteral#gsub, но не StringLiteral#scan.

В дополнение к этим примитивным типам ранее упомянутые типы макросов предоставляют свой собственный набор методов, чтобы мы могли получать информацию о связанном типе, например:

• Тип возвращаемого значения, его видимость или аргументы метода.

• Тип/значение по умолчанию аргумента метода.

• Какие аргументы объединения/обобщения имеет тип, если таковые имеются.

Конечно, их слишком много, чтобы их здесь упоминать, поэтому я предлагаю просмотреть документацию по API для получения полного списка. А пока давайте применим некоторые из этих методов:

class Foo

  def hello(one : Int32, two, there, four : Bool, five :

    String?)

    {% begin %}

      {{"#{@def.name} has #{@def.args.size} arguments"}}

      {% typed_arguments = @def.args.select(&.restriction) %}

      {{"with #{typed_arguments.size} typed

        arguments"}}

      {{"and is a #{@def.visibility.id} method"}}

    {% end %}

  end

end

Foo.new.hello 1, 2, 3, false, nil

Эта программа выведет следующее:

"hello has 5 arguments"

"with 3 typed arguments"

"and is a public method"

Первая строка выводит имя метода и количество его аргументов через ArrayLiteral#size, поскольку Def#args возвращает ArrayLiteral(Arg). Затем мы используем метод ArrayLiteral#select, чтобы получить массив, содержащий только аргументы, имеющие ограничение типа. Arg#restriction возвращает TypeNode на основе типа ограничения или Nop, которое является ложным значением и используется для представления пустого узла. Наконец, мы используем Def#visibility, чтобы узнать уровень видимости метода. Он возвращает символический литерал, поэтому мы вызываем для него #id, чтобы получить его общее представление.

Существует еще одна специальная макропеременная @top_level, которая возвращает TypeNode, представляющий пространство имен верхнего уровня. Если мы не воспользуемся этим, единственный другой способ получить к нему доступ — это вызвать @type в пространстве имен верхнего уровня, что сделает невозможным ссылку на него внутри другого типа. Давайте посмотрим, как можно использовать эту переменную:

A_CONSTANT = 0

module Foo; end

{% if @top_level.has_constant?("A_CONSTANT") && @top_level

  .has_constant?("Foo") %}

  puts "this is printed"

{% else %}

  puts "this is not printed"

{% end %}

В этом примере мы использовали TypeNode#has_constant?, который возвращает BoolLiteral, если связанный TypeNode имеет предоставленную константу, предоставленную в виде StringLiteral, SymbolLiteral или MacroId (тип, который вы получаете при вызове #id для другого типа). Этот метод работает как для реальных констант, так и для типов.

Понимание API макросов имеет решающее значение для написания макросов, использующих информацию, полученную из типа и/или метода. Я настоятельно рекомендую прочитать документацию по API для некоторых типов макросов, о которых мы говорили в этом разделе, чтобы полностью понять, какие методы

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

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