# Глава 8. Обобщения Итератор `items`, который только что был рассмотрен, работает только с `seq[Point]`. Однако в коде не используются никакие возможности `Point`, например он не обращается к `point.x`. Бывает нужно перебрать все `seq[T]`, где `T` может быть любого типа. **Nim** поддерживает такие переменные типов с помощью _обобщений_: ```nim iterator items[T](s: seq[T]): T = # 1 for i in 0 ..< s.len: yield s[i] for x in items(@[1, 2, 3]): discard # 2 for x in items(@["1", "2", "3"]): discard # 3 ``` - 1 Итератор `items` работает с любым типом `seq[T]`. Он возвращает значения типа `T`. - 2 `@[1, 2, 3]` имеет тип `seq[int]`. При вызове `items` его переменная типа `T` определяется как `int`. - 3 `@["1", "2", "3"]` имеет тип `seq[string]`. При вызове `items` его переменная типа `T` определяется как `string`. **Nim** использует специализацию для обобщений. Каждый новый конкретный тип, такой как `int` или `string`, генерирует специализированный код, что не требует дополнительных затрат во время выполнения. Обобщенными могут быть не только `procs` и итераторы, но и типы: ```nim type Point[T] = object # 1 x, y: T # 2 var p: Point[float] # 3 p = Point[float](x: 1.0, y: 3.0) # 4 ``` - 1 Тип `Point` параметризуется переменной типа `T`. - 2 `T` используется для объявления полей `x` и `y`. - 3 Объявлена переменная с именем `p`, имеющая тип `Point[float]` - 4 Для создания объекта `Point` также требуется явный тип; в данном случае `float`. К сожалению, вывод типов не работает при создании объектов, `Point(x: 1.0, y: 3.0)` не допускается без параметризации явным типом. Вероятно, в будущем это ограничение будет снято. Если тип параметризован переменной типа `T`, то операции с ним обычно тоже должны быть параметризованы. Например, процедура `drawHorizontalLine` стала бы такой: ```nim proc drawHorizontalLine[T](a, b: Point[T]) = if b.x < a.x: drawHorizontalLine(b, a) else: for x in a.x .. b.x: putPixel(x, a.y) ``` `drawHorizontalLine` принимает два параметра одного типа `Point[T]`. Другими словами, вызов вида `drawHorizontalLine(Point[float](x: 2.0, y: 3.0), Point[int](x: 2, y: 3))` будет отклонен, потому что один `T` не может быть одновременно `float` и `int`. Мы можем использовать переменные разных типов, чтобы обеспечить работу с несколькими параметрическими типами одновременно `drawHorizontalLine(Point[float](x: 2.0, y: 3.0), Point[int](x: 2, y: 3))`: ```nim proc drawHorizontalLine[T, U](a: Point[T]; b: Point[U]) = if b.x < a.x: drawHorizontalLine(b, a) else: for x in a.x .. b.x: putPixel(x, a.y) ``` то предполагает, что у нас есть итератор `..`, который может работать со смешанными типами. Например, можно реализовать такой итератор следующим образом: ```nim iterator `..`[T, U](a: T, b: U): U = # 1 var i = U(a) # 2 while i <= b: # 3 yield i inc i # 4 ``` - 1 несколько произвольно решено, что создаваемые значения относятся к типу `U`, а не к `T`. - 2 С помощью `U(a)` мы преобразуем исходное значение `a` в тип `U`. В **Nim** преобразование типа выглядит как вызов функции. - 3 Здесь делается предположение, что тип `U` предлагает оператор `<=`. Это предположение не зафиксировано — обобщения в **Nim** могут быть недостаточно конкретными. 4 Здесь предположено, что тип `U` поддерживает подходящую операцию `inc`. Переменная типа `T` обычно не указывается в **Nim**; требования являются только неявными, и тип универсального кода проверяется только при создании экземпляра универсального кода: ```nim for x in "a".."b": ... # неправильно for x in 0 .. 3: ... # правильно for x in 0 .. 3.0: ... # неправильные типы: `float` не является `inc` ```