diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt index c1c6467e70..1fca531aa5 100644 --- a/doc/manual/generics.txt +++ b/doc/manual/generics.txt @@ -148,8 +148,8 @@ as `type constraints`:idx: of the generic type parameter: onlyIntOrString("xy", 50) # invalid as 'T' cannot be both at the same time By default, during overload resolution each named type class will bind to -exactly one concrete type. Here is an example taken directly from the system -module to illustrate this: +exactly one concrete type. We call such type classes `bind once`:idx: types. +Here is an example taken directly from the system module to illustrate this: .. code-block:: nim proc `==`*(x, y: tuple): bool = @@ -161,7 +161,8 @@ module to illustrate this: 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. +to allow each param matching the type class to bind to a different type. Such +type classes are called `bind many`:idx: 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 @@ -211,34 +212,375 @@ Concepts are written in the following form: Comparable = concept x, y (x < y) is bool - Container[T] = concept c - c.len is Ordinal - items(c) is T - for value in c: - type(value) is T + Stack[T] = concept s, var v + s.pop() is T + v.push(T) + + s.len is Ordinal + + for value in s: + value is T The concept is a match if: a) all of the expressions within the body can be compiled for the tested type -b) all statically evaluatable boolean expressions in the body must be true +b) all statically evaluable boolean expressions in the body must be true The identifiers following the ``concept`` keyword represent instances of the -currently matched type. These instances can act both as variables of the type, -when used in contexts where a value is expected, and as the type itself when -used in contexts where a type is expected. +currently matched type. You can apply any of the standard type modifiers such +as ``var``, ``ref``, ``ptr`` and ``static`` to denote a more specific type of +instance. You can also apply the `type` modifier to create a named instance of +the type itself: + +.. code-block:: nim + type + MyConcept = concept x, var v, ref r, ptr p, static s, type T + ... + +Within the concept body, types can appear in positions where ordinary values +and parameters are expected. This provides a more convenient way to check for +the presence of callable symbols with specific signatures: + +.. code-block:: nim + type + OutputStream = concept var s + s.write(string) + +In order to check for symbols accepting ``typedesc`` params, you must prefix +the type with an explicit ``type`` modifier. The named instance of the type, +following the ``concept`` keyword is also considered an explicit ``typedesc`` +value that will be matched only as a type. + +.. code-block:: nim + type + # Let's imagine a user-defined casting framework with operators + # such as `val.to(string)` and `val.to(JSonValue)`. We can test + # for these with the following concept: + MyCastables = concept x + x.to(type string) + x.to(type JSonValue) + + # Let's define a couple of concepts, known from Algebra: + AdditiveMonoid* = concept x, y, type T + x + y is T + T.zero is T # require a proc such as `int.zero` or 'Position.zero' + + AdditiveGroup* = concept x, y, type T + x is AdditiveMonoid + -x is T + x - y is T Please note that the ``is`` operator allows one to easily verify the precise type signatures of the required operations, but since type inference and -default parameters are still applied in the provided block, it's also possible +default parameters are still applied in the concept body, it's also possible to encode usage protocols that do not reveal implementation details. -Much like generics, concepts are instantiated exactly -once for each tested type and any static code included within them is also -executed once. +Much like generics, concepts are instantiated exactly once for each tested type +and any static code included within the body is executed only once. -**Hint**: Since concepts are still very rough at the edges there is a -command line switch ``--reportConceptFailures:on`` to make debugging -concept related type failures more easy. + +Concept diagnostics +------------------- + +By default, the compiler will report the matching errors in concepts only when +no other overload can be selected and a normal compilation error is produced. +When you need to understand why the compiler is not matching a particular +concept and, as a result, a wrong overload is selected, you can apply the +``explain`` pragma to either the concept body or a particular call-site. + +.. code-block:: nim + type + MyConcept {.explain.} = concept ... + + overloadedProc(x, y, z) {.explain.} + +This will provide Hints in the compiler output either every time the concept is +not matched or only on the particular call-site. + + +Generic concepts and type binding rules +--------------------------------------- + +The concept types can be parametric just like the regular generic types: + +.. code-block:: nim + ### matrixalgo.nim + type + AnyMatrix*[R, C: static[int]; T] = concept m, var mvar, type M + M.ValueType is T + M.Rows == R + M.Cols == C + + m[int, int] is T + mvar[int, int] = T + + AnySquareMatrix*[N: static[int], T] = AnyMatrix[N, N, T] + + proc transpose*[R, C, T](m: AnyMatrix[R, C, T]): m.type.basis[C, R, T] = + for r in 0 ..