|
|
@@ -1,51 +1,64 @@
|
|
|
# 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 items[T](s: seq[T]): T = 1
|
|
|
+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:
|
|
|
+
|
|
|
+```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 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 = Point[float](x: 1.0, y: 3.0) 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
|
|
|
+ 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:
|
|
|
+
|
|
|
+```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 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
|
|
|
+
|
|
|
+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]) =
|
|
|
+
|
|
|
+```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 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` 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
|
|
|
+
|
|
|
+```nim
|
|
|
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:
|
|
|
@@ -53,24 +66,30 @@ drawHorizontalLine(Point[float](x: 2.0, y: 3.0), Point[int](x: 2, y: 3)):
|
|
|
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
|
|
|
+```
|
|
|
+
|
|
|
+This assumes that we have an iterator `..` that can handle mixed types. We
|
|
|
could provide such an iterator like this:
|
|
|
-36
|
|
|
- iterator `..`[T, U](a: T, b: U): U = 1
|
|
|
- var i = U(a) 2
|
|
|
- while i <= b: 3
|
|
|
+
|
|
|
+```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 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
|
|
|
+ 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`
|
|
|
+
|
|
|
+```nim
|
|
|
+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`
|
|
|
+```
|