@[MyAnnotation]
class Klass
end
@[MyAnnotation]
module MyModule
end
К одному и тому же элементу также можно применить несколько аннотаций:
annotation Ann1; end
annotation Ann2; end
@[Ann1]
@[Ann2]
@[Ann2]
def foo
end
В этом конкретном контексте на самом деле нет смысла использовать более одной аннотации, поскольку нет способа отличить их друг от друга; однако это будет иметь больше смысла, если вы добавите данные в аннотацию, что является темой следующего раздела.
Итак, аннотации — это то, что можно применять к различным вещам в коде для хранения метаданных о них.
Примером этого может быть, скажем, у вас есть модель ORM, которую вы хотите проверить. Например, если одна из установленных вами библиотек использует собственный макрос, такой как column id : Int64
, это может сделать другие библиотеки нефункциональными, поскольку аннотация может быть неправильно применена к переменной экземпляра или методу. Однако если все библиотеки используют аннотации, то все они работают со стандартными переменными экземпляра Crystal, поэтому у библиотек нет возможности конфликтовать, и это делает все более естественным.
Кроме того, аннотации более ориентированы на будущее и более гибки по сравнению с определениями макросов для этого конкретного варианта использования. Далее давайте поговорим о том, как хранить данные в аннотации.
Хранение данных в аннотациях
Подобно методу, аннотация поддерживает как позиционные, так и именованные аргументы:
annotation MyAnnotation
end
@[MyAnnotation(name: "value", id: 123)]
def foo; end
@[MyAnnotation("foo", 123, false)]
def bar; end
В этом примере мы определили два пустых метода, к каждому из которых применена аннотация. Первый использует исключительно именованные аргументы, а второй использует исключительно позиционные аргументы. Лучший пример применения нескольких аннотаций одного и того же типа можно продемонстрировать, когда в каждую аннотацию включены данные. Вот пример:
annotation MyAnnotation; end
@[MyAnnotation(1, enabled: false)]
@[MyAnnotation(2)]
def foo
end
Поскольку значения в каждой аннотации могут быть разными, связанная библиотека может создать несколько методов или переменных, например, на основе каждой аннотации и данных в ней. Однако эти данные бесполезны, если вы не можете получить к ним доступ! Давайте посмотрим, как это сделать дальше.
Чтение аннотаций
В Crystal вы обычно вызываете метод объекта, чтобы получить доступ к некоторым данным, хранящимся внутри. Аннотации ничем не отличаются. Тип Annotation
предоставляет три метода, которые можно использовать для доступа к данным, определенным в аннотации, различными способами. Однако прежде чем вы сможете получить доступ к данным в аннотации, вам необходимо получить ссылку на экземпляр Annotation
. Это можно сделать, передав тип Annotation
методу #annotation
, определенному для типов, поддерживающих аннотации, включая TypeNode
, Def
и MetaVar
. Например, мы можем использовать этот метод для печати аннотации, примененной к определенному классу или методу, если таковой имеется:
annotation MyAnnotation; end
@[MyAnnotation]
class MyClass
def foo
{{pp @type.annotation MyAnnotation}}
{{pp @def.annotation MyAnnotation}}
end
end
MyClass.new.foo
Метод #annotation
вернет NilLiteral
, если аннотация указанного типа не применена. Теперь, когда у нас есть доступ к примененной аннотации, мы готовы начать чтение из нее данных!
Первый, наиболее простой способ — использование метода #[]
, который может показаться знакомым, поскольку он также используется, среди прочего, как часть типов Array
и Hash
. Этот метод имеет две формы: первая принимает NumberLiteral
и возвращает позиционное значение по предоставленному индексу. Другая форма принимает StringLiteral
, SymbolLiteral
или MacroId
и возвращает значение с предоставленным ключом. Оба этих метода вернут NilLiteral
, если по указанному индексу или указанному ключу не существует значения.