アノテーション¶
アノテーションは、ソースコードの特定の機能にメタデータを追加するために使用できます。型、メソッド、インスタンス変数、およびメソッド/マクロパラメータにアノテーションを付けることができます。標準ライブラリの JSON::Field などのユーザー定義アノテーションは、annotation
キーワードを使用して定義されます。コンパイラによって多数の 組み込みアノテーション が提供されています。
ユーザーは annotation
キーワードを使用して独自のアノテーションを定義できます。これは、class
や struct
を定義するのと似ています。
annotation MyAnnotation
end
アノテーションは、以下を含む様々なアイテムに適用できます。
- インスタンスメソッドとクラスメソッド
- インスタンス変数
- クラス、構造体、列挙型、モジュール
- メソッドとマクロのパラメータ(ただし、後者は現在アクセスできません)
annotation MyAnnotation
end
@[MyAnnotation]
def foo
"foo"
end
@[MyAnnotation]
class Klass
end
@[MyAnnotation]
module MyModule
end
def method1(@[MyAnnotation] foo)
end
def method2(
@[MyAnnotation]
bar
)
end
def method3(@[MyAnnotation] & : String ->)
end
アプリケーション¶
アノテーションは、特定のインスタンス変数、型、またはメソッドに関するメタデータを格納し、マクロを使用してコンパイル時に読み取ることができるようにするために最適です。アノテーションの主な利点の1つは、インスタンス変数/メソッドに直接適用されるため、これらのプロパティ/メソッドを作成するために標準マクロが不要になるため、クラスがより自然に見えることです。
アノテーションのいくつかのアプリケーション
オブジェクトのシリアライズ¶
インスタンス変数に適用すると、そのインスタンス変数をシリアライズする必要があるかどうか、またはどのキーでシリアライズするかを決定するアノテーションを作成します。Crystalの JSON::Serializable
と YAML::Serializable
はこの例です。
ORM¶
アノテーションを使用して、プロパティをORMカラムとして指定できます。インスタンス変数の名前と型は、アノテーションに加えて TypeNode
から読み取ることができます。ORM固有のマクロは不要です。アノテーション自体を使用して、カラムに関するメタデータ(NULL許容かどうか、カラムの名前、主キーかどうかなど)を格納することもできます。
フィールド¶
データはアノテーション内に格納できます。
annotation MyAnnotaion
end
# The fields can either be a key/value pair
@[MyAnnotation(key: "value", value: 123)]
# Or positional
@[MyAnnotation("foo", 123, false)]
キー/バリュー¶
アノテーションのキー/バリューペアの値は、[]
メソッドを使用してコンパイル時にアクセスできます。
annotation MyAnnotation
end
@[MyAnnotation(value: 2)]
def annotation_value
# The name can be a `String`, `Symbol`, or `MacroId`
{{ @def.annotation(MyAnnotation)[:value] }}
end
annotation_value # => 2
named_args
メソッドを使用して、アノテーションのすべてのキー/バリューペアを NamedTupleLiteral
として読み取ることができます。このメソッドは、すべてのアノテーションにデフォルトで定義されており、適用される各アノテーションに固有です。
annotation MyAnnotation
end
@[MyAnnotation(value: 2, name: "Jim")]
def annotation_named_args
{{ @def.annotation(MyAnnotation).named_args }}
end
annotation_named_args # => {value: 2, name: "Jim"}
このメソッドは NamedTupleLiteral
を返すため、その型のすべての メソッド を使用できます。特に、アノテーション引数をメソッドに渡すのを容易にする #double_splat
です。
annotation MyAnnotation
end
class SomeClass
def initialize(@value : Int32, @name : String); end
end
@[MyAnnotation(value: 2, name: "Jim")]
def new_test
{% begin %}
SomeClass.new {{ @def.annotation(MyAnnotation).named_args.double_splat }}
{% end %}
end
new_test # => #<SomeClass:0x5621a19ddf00 @name="Jim", @value=2>
位置引数¶
[]
メソッドを使用して、コンパイル時に位置引数にアクセスできます。ただし、一度にアクセスできるインデックスは1つだけです。
annotation MyAnnotation
end
@[MyAnnotation(1, 2, 3, 4)]
def annotation_read
{% for idx in [0, 1, 2, 3, 4] %}
{% value = @def.annotation(MyAnnotation)[idx] %}
pp "{{ idx }} = {{ value }}"
{% end %}
end
annotation_read
# Which would print
"0 = 1"
"1 = 2"
"2 = 3"
"3 = 4"
"4 = nil"
args
メソッドを使用して、アノテーションのすべての位置引数を TupleLiteral
として読み取ることができます。このメソッドは、すべてのアノテーションにデフォルトで定義されており、適用される各アノテーションに固有です。
annotation MyAnnotation
end
@[MyAnnotation(1, 2, 3, 4)]
def annotation_args
{{ @def.annotation(MyAnnotation).args }}
end
annotation_args # => {1, 2, 3, 4}
TupleLiteral
の戻り値の型は反復可能であるため、前の例をより良い方法で書き直すことができます。さらに、TupleLiteral
のすべての メソッド も使用できます。
annotation MyAnnotation
end
@[MyAnnotation(1, "foo", true, 17.0)]
def annotation_read
{% for value, idx in @def.annotation(MyAnnotation).args %}
pp "{{ idx }} = #{{{ value }}}"
{% end %}
end
annotation_read
# Which would print
"0 = 1"
"1 = foo"
"2 = true"
"3 = 17.0"
読み込み¶
アノテーションは、TypeNode
、Def
、MetaVar
、または Arg
から .annotation(type : TypeNode)
メソッドを使用して読み取ることができます。このメソッドは、指定された型の適用されたアノテーションを表す Annotation
オブジェクトを返します。
注記
同じ型の複数のアノテーションが適用されている場合、.annotation
メソッドは*最後*のものを返します。
@type
変数と @def
変数を使用して、.annotation
メソッドを使用する TypeNode
または Def
オブジェクトを取得できます。ただし、TypeNode
の他のメソッドを使用して TypeNode
/Def
型を取得することもできます。たとえば、それぞれ TypeNode.all_subclasses
または TypeNode.methods
です。
ヒント
TypeNode
を取得するより高度な方法については、parse_type
メソッドを確認してください。
TypeNode.instance_vars
を使用して、インスタンス変数 `MetaVar` オブジェクトの配列を取得できます。これにより、それらのインスタンス変数に定義されたアノテーションを読み取ることができます。
注記
`TypeNode.instance_vars` は現在、インスタンス/クラスメソッドのコンテキストでのみ機能します。
annotation MyClass
end
annotation MyMethod
end
annotation MyIvar
end
annotation MyParameter
end
@[MyClass]
class Foo
pp {{ @type.annotation(MyClass).stringify }}
@[MyIvar]
@num : Int32 = 1
@[MyIvar]
property name : String = "jim"
def properties
{% for ivar in @type.instance_vars %}
pp {{ ivar.annotation(MyIvar).stringify }}
{% end %}
end
end
@[MyMethod]
def my_method
pp {{ @def.annotation(MyMethod).stringify }}
end
def method_params(
@[MyParameter(index: 0)]
value : Int32,
@[MyParameter(index: 1)] metadata,
@[MyParameter(index: 2)] & : -> String
)
pp {{ @def.args[0].annotation(MyParameter).stringify }}
pp {{ @def.args[1].annotation(MyParameter).stringify }}
pp {{ @def.block_arg.annotation(MyParameter).stringify }}
end
Foo.new.properties
my_method
method_params 10, false do
"foo"
end
pp {{ Foo.annotation(MyClass).stringify }}
# Which would print
"@[MyClass]"
"@[MyIvar]"
"@[MyIvar]"
"@[MyMethod]"
"@[MyParameter(index: 0)]"
"@[MyParameter(index: 1)]"
"@[MyParameter(index: 2)]"
"@[MyClass]"
警告
アノテーションは、型指定されたブロックパラメータからのみ読み取ることができます。https://github.com/crystal-lang/crystal/issues/5334 を参照してください。
複数アノテーションの読み込み¶
#annotations
メソッドは、型に付けられた*すべて*のアノテーションの ArrayLiteral
を返します。必要に応じて、#annotations(type : TypeNode)
メソッドを持つ TypeNode
引数は、指定された*型*のアノテーションのみをフィルタリングします。
annotation MyAnnotation; end
annotation OtherAnnotation; end
@[MyAnnotation("foo")]
@[MyAnnotation(123)]
@[OtherAnnotation(456)]
def annotation_read
{% for ann in @def.annotations(MyAnnotation) %}
pp "{{ann.name}}: {{ ann[0].id }}"
{% end %}
puts
{% for ann in @def.annotations %}
pp "{{ann.name}}: {{ ann[0].id }}"
{% end %}
end
annotation_read
# Which would print:
"MyAnnotation: foo"
"MyAnnotation: 123"
"MyAnnotation: foo"
"MyAnnotation: 123"
"OtherAnnotation: 456"