Implicit generics

A type class can be used directly as the parameter’s type.

  1. # create a type class that will match all tuple and object types
  2. type RecordType = tuple or object
  3. proc printFields(rec: RecordType) =
  4. for key, value in fieldPairs(rec):
  5. echo key, " = ", value

Procedures utilizing type classes in such manner are considered to be implicitly generic. They will be instantiated once for each unique combination of param types used within the program.

By default, during overload resolution each named type class will bind to exactly one concrete type. We call such type classes bind once types. Here is an example taken directly from the system module to illustrate this:

  1. proc `==`*(x, y: tuple): bool =
  2. ## requires `x` and `y` to be of the same tuple type
  3. ## generic ``==`` operator for tuples that is lifted from the components
  4. ## of `x` and `y`.
  5. result = true
  6. for a, b in fields(x, y):
  7. if a != b: result = false

Alternatively, the distinct type modifier can be applied to the type class to allow each param matching the type class to bind to a different type. Such type classes are called bind many types.

Procs written with the implicitly generic style will often need to refer to the type parameters of the matched generic type. They can be easily accessed using the dot syntax:

  1. type Matrix[T, Rows, Columns] = object
  2. ...
  3. proc `[]`(m: Matrix, row, col: int): Matrix.T =
  4. m.data[col * high(Matrix.Columns) + row]

Here are more examples that illustrate implicit generics:

  1. proc p(t: Table; k: Table.Key): Table.Value
  2. # is roughly the same as:
  3. proc p[Key, Value](t: Table[Key, Value]; k: Key): Value
  1. proc p(a: Table, b: Table)
  2. # is roughly the same as:
  3. proc p[Key, Value](a, b: Table[Key, Value])
  1. proc p(a: Table, b: distinct Table)
  2. # is roughly the same as:
  3. proc p[Key, Value, KeyB, ValueB](a: Table[Key, Value], b: Table[KeyB, ValueB])

typedesc used as a parameter type also introduces an implicit generic. typedesc has its own set of rules:

  1. proc p(a: typedesc)
  2. # is roughly the same as:
  3. proc p[T](a: typedesc[T])

typedesc is a “bind many” type class:

  1. proc p(a, b: typedesc)
  2. # is roughly the same as:
  3. proc p[T, T2](a: typedesc[T], b: typedesc[T2])

A parameter of type typedesc is itself usable as a type. If it is used as a type, it’s the underlying type. (In other words, one level of “typedesc”-ness is stripped off:

  1. proc p(a: typedesc; b: a) = discard
  2. # is roughly the same as:
  3. proc p[T](a: typedesc[T]; b: T) = discard
  4. # hence this is a valid call:
  5. p(int, 4)
  6. # as parameter 'a' requires a type, but 'b' requires a value.