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 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
seq[T]. It produces values of type T.@[1, 2, 3] has type seq[int]. When items is called its type variable T is inferred to be int.@["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 = Point[float](x: 1.0, y: 3.0) # 4
Point type is parametrized by a type variable T.T is used to declare the fields x and y.p is declared that is of type Point[float]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 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 takes two parameters of the same type Point[T]. In other
words, a call like drawHorizontalLine(Point[float](x: 2.0, y: 3.0),
Point[int](x: 2, y: 3)) 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(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)
This assumes that we have an iterator .. that can handle mixed types. We
could provide such an iterator like this:
iterator `..`[T, U](a: T, b: U): U = # 1
var i = U(a) # 2
while i <= b: # 3
yield i
inc i # 4
U and not of T.U(a) we convert the starting value a to type U. In Nim a type conversion looks like a function call.U offers an operator <=. This assumption is not written down — generics in Nim can be under-specified.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`