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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 51 52 53 54 55 56 57 58 59 ... 75
Перейти на страницу:
нам нужно обернуть эту логику в начало/конец, чтобы сделать все допустимым синтаксисом Crystal. Затем мы используем метод #instance_vars для экземпляра TypeNode, полученного с помощью специальной макропеременной @type. Этот метод возвращает Array(MetaVar), который включает информацию о каждой переменной экземпляра, такую как ее имя, тип и значение по умолчанию.

Наконец, мы перебираем каждую переменную экземпляра с помощью цикла for, используя строковое представление имени переменной экземпляра в качестве ключа и, конечно же, ее значение в качестве значения хеша. Запуск этой версии программы дает тот же результат, что и раньше, но с двумя основными преимуществами:

• Он автоматически обрабатывает вновь добавленные/удаленные переменные экземпляра.

• Он будет включать переменные экземпляра, определенные для дочерних типов, поскольку макрос расширяется для каждого конкретного подкласса, поскольку он использует макропеременную @type.

Подобно итерации переменных экземпляра, доступ к переменным класса также можно получить с помощью метода TypeNode#class_vars. Однако есть одна серьезная ошибка при переборе переменных экземпляра/класса типа.

ПРЕДУПРЕЖДЕНИЕ

Доступ к переменным экземпляра возможен только в контексте метода. Попытка сделать это вне метода всегда приведет к получению пустого массива, даже если используется в ловушке завершения макроса.

По сути, это ограничение компилятора Crystal на данный момент, которое может быть реализовано в той или иной форме в будущем. Но до тех пор лучше иметь это в виду, чтобы не тратить время на отладку чего-то, что просто не будет работать. Посетите https://github.com/crystal-lang/crystal/issues/7504 для получения дополнительной информации об этом ограничении.

Другой вариант использования итерации переменных экземпляра — это добавление переменных экземпляра к некоторой внешней логике, которая может быть включена в модуль. Например, предположим, что у нас есть модуль Incrementable, который определяет один метод #increment, который, как следует из названия, будет увеличивать определенные выбранные переменные. Реализация этого метода может использовать @type.instance_vars вместе с ArrayLiteral#select, чтобы определить, какие переменные следует увеличить.

Прежде всего, давайте посмотрим на код модуля Incrementable:

module Incrementable

  annotation Increment; end

    def increment

      {% for ivar in @type.instance_vars.select &.annotation Increment %}

      @{{ivar}} += 1

    {% end %}

  end

end

Сначала мы определяем наш модуль вместе с аннотацией внутри него. Затем мы определяем метод, который фильтрует переменные экземпляра типа только для тех, к которым применена аннотация. Для каждой из этих переменных мы увеличиваем ее на единицу. Далее давайте посмотрим на тип, который будет включать в себя этот модуль:

class MyClass

  include Incrementable

  getter zero : Int32 = 0

  @[Incrementable::Increment]

  getter one : Int32 = 1

  getter two : Int32 = 2 @[Incrementable::Increment]

  getter three : Int32 = 3

end

Это довольно простой класс, который просто включает в себя наш модуль, определяет некоторые переменные экземпляра с помощью макроса getter и применяет аннотацию, определенную в модуле, к паре переменных. Мы можем протестировать наш код, создав и запустив следующую небольшую программу:

obj = MyClass.new

pp obj

obj.increment

pp obj

В этой программе мы создаем новый экземпляр нашего класса, который мы определили в последнем примере, печатаем состояние этого объекта, вызываем метод increment, а затем снова печатаем состояние объекта. Первая строка вывода показывает, что значение каждой переменной экземпляра соответствует имени переменной. Однако вторая строка вывода показывает, что переменные номер один и три действительно были увеличены на единицу.

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

Итерационные типы

Многое из того, о чем мы говорили и продемонстрировали в последнем разделе, также можно применить и к самим типам. Одним из основных преимуществ перебора типов является то, что они не ограничены теми же ограничениями, что и переменные экземпляра. Другими словами, вам не обязательно находиться в контексте метода, чтобы перебирать типы. Благодаря этому возможности практически безграничны!

Вы можете перебирать типы в контексте другого класса для генерации кода, перебирать на верхнем уровне для создания дополнительных типов или даже внутри метода, чтобы построить своего рода конвейер, используя аннотации для определения порядка.

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

1. По всем или прямым подклассам родительского типа.

2. Типы, включающие определенный модуль.

3. Типы, к которым применяются определенные аннотации*

4. Некоторая комбинация предыдущих трех способов.

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

Самый распространенный способ перебора типов — через подклассы родительского типа. Это могут быть либо все подклассы этого типа, либо только прямые подклассы. Давайте посмотрим, как бы вы это сделали.

Итерация подклассов типа

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

abstract class Vehicle; end

abstract class Car < Vehicle; end

class SUV < Vehicle; end

class Sedan < Car; end

class Van < Car; end

Первое, что нам нужно, это TypeNode родительского типа, подклассы которого мы хотим перебрать. В нашем случае это будет Vehicle, но это не обязательно должен быть самый верхний тип. Мы могли бы с тем же успехом выбрать Car, если бы она лучше соответствовала нашим потребностям.

Если вы помните первую главу этой части, мы смогли получить TypeNode с помощью специальной макропеременной @type. Однако это будет работать только в том случае, если

1 ... 51 52 53 54 55 56 57 58 59 ... 75
Перейти на страницу:

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