abstract class Foo
def foo; end
end
module Bar
def bar; end
end
class Baz < Foo
include Bar
def baz; end
def foo(value : Int32); end
def foo(value : String); end
def bar(x); end
end
baz = Baz.new
baz.bar 1
baz.bar false
{{pp Baz.methods.map &.name}}
Запуск этого приведет к следующему:
[baz, foo, foo, bar]
Обратите внимание, что, как и в случае с методом #includers
, выводятся только методы, явно определенные внутри типа. Также обратите внимание, что метод #foo
включается один раз для каждой из его перегрузок. Однако, несмотря на то, что #bar вызывается с двумя уникальными типами, он включается только один раз.
Логика фильтрации, о которой мы говорили в последнем разделе, также применима к итеративным методам. Проверка аннотаций может быть простым способом Incrementable
из первого раздела, вы легко можете сделать что-то подобное, но заменив переменные экземпляра методами. Методы также обладают дополнительной гибкостью, поскольку их не нужно повторять в контексте метода.
Если вы помните раздел об итерации переменных экземпляра ранее в этой главе, для доступа к переменным класса существовал специальный метод TypeNode#class_vars
. В случае методов класса эквивалентного метода не существует. Однако их можно перебирать. В большинстве случаев TypeNode
будет представлять тип экземпляра типа, поэтому он используется для перебора переменных экземпляра или методов экземпляра этого типа. Однако существует метод, который можно использовать для получения другого TypeNode
, представляющего метакласс этого типа, из которого мы можем получить доступ к методам его класса. Существует также метод, который возвращает тип экземпляра, если TypeNode
представляет тип класса.
Этими методами являются TypeNode#class
и TypeNode#instance
. Например, если у вас есть TypeNode
, представляющий тип MyClass
, первый метод вернет новый TypeNode
, представляющий MyClass.class
, тогда как последний метод превратит MyClass.class
в MyClass
. Когда у нас есть тип класса TypeNode
, это так же просто, как вызвать для него #methods
; например:
class Foo
def self.foo; end
def self.bar; end
end
{{pp Foo.class.methods.map &.name}}
Запуск этого приведет к следующему:
[allocate, foo, bar]
Вам может быть интересно, откуда взялся метод allocate
. Этот метод автоматически добавляется Crystal для использования в конструкторе, чтобы выделить память, необходимую для его создания. Учитывая, что вы, скорее всего, не захотите включать этот метод в свою логику, обязательно предусмотрите способ его отфильтровать.
Поскольку сами типы можно повторять, вы можете объединить эту концепцию с методами итерации. Другими словами, можно перебирать типы, а затем перебирать каждый из методов этого типа. Это может быть невероятно мощным средством автоматической генерации кода, так что конечному пользователю нужно только применить некоторые аннотации или наследовать/включить какой-либо другой тип.
Резюме
И вот оно у вас есть; как анализировать переменные, типы и методы экземпляра/класса во время компиляции! Этот метод метапрограммирования можно использовать для создания мощной логики генерации кода, которая может упростить расширение и использование приложений, одновременно делая приложение более надежным за счет снижения вероятности опечаток или ошибок пользователя.
Далее, в последней главе этой части, мы рассмотрим несколько примеров того, как все изученные до сих пор концепции метапрограммирования можно объединить в более сложные шаблоны/функции.
Дальнейшее чтение
Как упоминалось ранее, в TypeNode
есть гораздо больше методов, которые находятся за пределами области видимости. Однако я настоятельно рекомендую ознакомиться с документацией по адресу https://crystal-lang.org/api/Crystal/Macros/TypeNode.html, чтобы узнать больше о том, какие дополнительные данные могут быть извлечены.
13. Расширенное использование макросов
В последних нескольких главах мы рассмотрели различные концепции метапрограммирования, такие как макросы, аннотации, и то, как их можно использовать вместе, чтобы обеспечить самоанализ типов, методов и переменных экземпляра во время компиляции. Однако по большей части мы использовали их самостоятельно. Эти концепции также можно комбинировать, чтобы создавать еще более мощные шаборны! В этой главе мы собираемся изучить некоторые из них, в том числе: