ch08.md 5.5 KB

Глава 8. Обобщения

Итератор items, который только что был рассмотрен, работает только с seq[Point]. Однако в коде не используются никакие возможности Point, например он не обращается к point.x. Бывает нужно перебрать все seq[T], где T может быть любого типа.

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 и итераторы, но и типы:

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 стала бы такой:

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)):

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)

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

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; требования являются только неявными, и тип универсального кода проверяется только при создании экземпляра универсального кода:

for x in "a".."b": ... # неправильно
for x in 0 .. 3: ...   # правильно
for x in 0 .. 3.0: ... # неправильные типы: `float` не является `inc`