ch08.md 3.3 KB

Chapter 8. Generics

The items iterator that we have just seen only works on seq[Point]. However, the code does not use any features of Point, it doesn’t access point.x, for example. We really want to iterate over every seq[T] where T can be any type. Nim supports such type variables via generics: iterator itemsT: 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 The items iterator works for any type seq[T]. It produces values of type T. 2 @[1, 2, 3] has type seq[int]. When items is called its type variable T is

inferred to be int.

3 @["1", "2", "3"] has type seq[string]. When items is called its type

variable T is inferred to be string.

Nim uses specialization for its generics. Every new concrete type like int or string produces specialized code, there is no runtime overhead. Not only procs and iterators but also types can be generic: type

Point[T] = object 1
   x, y: T 2

var p: Point[float] 3 p = Pointfloat 4

                                                                        35

1 The Point type is parametrized by a type variable T. 2 T is used to declare the fields x and y. 3 A variable of name p is declared that is of type Point[float] 4 Object construction of Point also requires an explicit type; in this case

 float.

Unfortunately type inference does not work for object construction, Point(x: 1.0, y: 3.0) is not allowed. This restriction will probably be removed in the future. If a type is parametrized by a type variable T operations on it usually have to be parametrized too. For example, our drawHorizontalLine proc would become: proc drawHorizontalLineT =

if b.x < a.x:
   drawHorizontalLine(b, a)
else:
   for x in a.x .. b.x:
     putPixel(x, a.y)

drawHorizontalLine takes two parameters of the same type Point[T]. In other words, a call like drawHorizontalLine(Pointfloat, Pointint) would be rejected because one T cannot be both float and int at the same time. We can use different type variables to allow for drawHorizontalLine(Pointfloat, Pointint): proc drawHorizontalLineT, U =

if b.x < a.x:
   drawHorizontalLine(b, a)
else:
   for x in a.x .. b.x:
     putPixel(x, a.y)

This assumes that we have an iterator .. that can handle mixed types. We could provide such an iterator like this: 36 iterator ..T, U: U = 1

var i = U(a) 2
while i <= b: 3
  yield i
  inc i   4

1 Somewhat arbitrarily we have decided that the produced values are of

type U and not of T.

2 Via U(a) we convert the starting value a to type U. In Nim a type

conversion looks like a function call.

3 We assume here that type U offers an operator <=. This assumption is not

written down — generics in Nim can be under-specified.

4 We assume here that type U offers a suitable operation inc. A type variable T is usually left under-specified in Nim; the requirements are only implicit and generic code is only type checked when the generic is instantiated: for x in "a".."b": ... # invalid for x in 0 .. 3: ... # valid for x in 0 .. 3.0: ... # invalid because float does not have inc