1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
Generics
========
Generics are Nim's means to parametrize procs, iterators or types with
`type parameters`:idx:. Depending on context, the brackets are used either to
introduce type parameters or to instantiate a generic proc, iterator or type.
The following example shows a generic binary tree can be modelled:
.. code-block:: nim
type
BinaryTreeObj[T] = object # BinaryTreeObj is a generic type with
# with generic param ``T``
le, ri: BinaryTree[T] # left and right subtrees; may be nil
data: T # the data stored in a node
BinaryTree[T] = ref BinaryTreeObj[T] # a shorthand for notational convenience
proc newNode[T](data: T): BinaryTree[T] = # constructor for a node
new(result)
result.data = data
proc add[T](root: var BinaryTree[T], n: BinaryTree[T]) =
if root == nil:
root = n
else:
var it = root
while it != nil:
var c = cmp(it.data, n.data) # compare the data items; uses
# the generic ``cmp`` proc that works for
# any type that has a ``==`` and ``<``
# operator
if c < 0:
if it.le == nil:
it.le = n
return
it = it.le
else:
if it.ri == nil:
it.ri = n
return
it = it.ri
iterator inorder[T](root: BinaryTree[T]): T =
# inorder traversal of a binary tree
# recursive iterators are not yet implemented, so this does not work in
# the current compiler!
if root.le != nil: yield inorder(root.le)
yield root.data
if root.ri != nil: yield inorder(root.ri)
var
root: BinaryTree[string] # instantiate a BinaryTree with the type string
add(root, newNode("hallo")) # instantiates generic procs ``newNode`` and
add(root, newNode("world")) # ``add``
for str in inorder(root):
writeln(stdout, str)
Is operator
-----------
The ``is`` operator checks for type equivalence at compile time. It is
therefore very useful for type specialization within generic code:
.. code-block:: nim
type
Table[Key, Value] = object
keys: seq[Key]
values: seq[Value]
when not (Key is string): # nil value for strings used for optimization
deletedKeys: seq[bool]
Type operator
-------------
The ``type`` (in many other languages called `typeof`:idx:) operator can
be used to get the type of an expression:
.. code-block:: nim
var x = 0
var y: type(x) # y has type int
If ``type`` is used to determine the result type of a proc/iterator/converter
call ``c(X)`` (where ``X`` stands for a possibly empty list of arguments), the
interpretation where ``c`` is an iterator is preferred over the
other interpretations:
.. code-block:: nim
import strutils
# strutils contains both a ``split`` proc and iterator, but since an
# an iterator is the preferred interpretation, `y` has the type ``string``:
var y: type("a b c".split)
Type Classes
------------
A type class is a special pseudo-type that can be used to match against
types in the context of overload resolution or the ``is`` operator.
Nim supports the following built-in type classes:
================== ===================================================
type class matches
================== ===================================================
``object`` any object type
``tuple`` any tuple type
``enum`` any enumeration
``proc`` any proc type
``ref`` any ``ref`` type
``ptr`` any ``ptr`` type
``var`` any ``var`` type
``distinct`` any distinct type
``array`` any array type
``set`` any set type
``seq`` any seq type
``auto`` any type
================== ===================================================
Furthermore, every generic type automatically creates a type class of the same
name that will match any instantiation of the generic type.
Type classes can be combined using the standard boolean operators to form
more complex type classes:
.. code-block:: nim
# create a type class that will match all tuple and object types
type RecordType = tuple or object
proc printFields(rec: RecordType) =
for key, value in fieldPairs(rec):
echo key, " = ", value
Procedures utilizing type classes in such manner are considered to be
`implicitly generic`:idx:. They will be instantiated once for each unique
combination of param types used within the program.
Nim also allows for type classes and regular types to be specified
as `type constraints`:idx: of the generic type parameter:
.. code-block:: nim
proc onlyIntOrString[T: int|string](x, y: T) = discard
onlyIntOrString(450, 616) # valid
onlyIntOrString(5.0, 0.0) # type mismatch
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:
.. code-block:: nim
proc `==`*(x, y: tuple): bool =
## requires `x` and `y` to be of the same tuple type
## generic ``==`` operator for tuples that is lifted from the components
## of `x` and `y`.
result = true
for a, b in fields(x, y):
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.
If a proc param doesn't have a type specified, Nim will use the
``distinct auto`` type class (also known as ``any``):
.. code-block:: nim
# allow any combination of param types
proc concat(a, b): string = $a & $b
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:
.. code-block:: nim
type Matrix[T, Rows, Columns] = object
...
proc `[]`(m: Matrix, row, col: int): Matrix.T =
m.data[col * high(Matrix.Columns) + row]
Alternatively, the `type` operator can be used over the proc params for similar
effect when anonymous or distinct type classes are used.
When a generic type is instantiated with a type class instead of a concrete
type, this results in another more specific type class:
.. code-block:: nim
seq[ref object] # Any sequence storing references to any object type
type T1 = auto
proc foo(s: seq[T1], e: T1)
# seq[T1] is the same as just `seq`, but T1 will be allowed to bind
# to a single type, while the signature is being matched
Matrix[Ordinal] # Any Matrix instantiation using integer values
As seen in the previous example, in such instantiations, it's not necessary to
supply all type parameters of the generic type, because any missing ones will
be inferred to have the equivalent of the `any` type class and thus they will
match anything without discrimination.
Concepts
--------
**Note**: Concepts are still in development.
Concepts, also known as "user-defined type classes", are used to specify an
arbitrary set of requirements that the matched type must satisfy.
Concepts are written in the following form:
.. code-block:: nim
type
Comparable = concept x, y
(x < y) is bool
Container[T] = concept c
c.len is Ordinal
items(c) is iterator
for value in c:
type(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
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.
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
to encode usage protocols that do not reveal implementation details.
As a special rule providing further convenience when writing concepts, any
type value appearing in a callable expression will be treated as a variable of
the designated type for overload resolution purposes, unless the type value was
passed in its explicit ``typedesc[T]`` form:
.. code-block:: nim
type
OutputStream = concept s
write(var s, string)
Much like generics, concepts are instantiated exactly
once for each tested type and any static code included within them is also
executed once.
Symbol lookup in generics
-------------------------
The symbol binding rules in generics are slightly subtle: There are "open" and
"closed" symbols. A "closed" symbol cannot be re-bound in the instantiation
context, an "open" symbol can. Per default overloaded symbols are open
and every other symbol is closed.
Open symbols are looked up in two different contexts: Both the context
at definition and the context at instantiation are considered:
.. code-block:: nim
type
Index = distinct int
proc `==` (a, b: Index): bool {.borrow.}
var a = (0, 0.Index)
var b = (0, 0.Index)
echo a == b # works!
In the example the generic ``==`` for tuples (as defined in the system module)
uses the ``==`` operators of the tuple's components. However, the ``==`` for
the ``Index`` type is defined *after* the ``==`` for tuples; yet the example
compiles as the instantiation takes the currently defined symbols into account
too.
A symbol can be forced to be open by a `mixin`:idx: declaration:
.. code-block:: nim
proc create*[T](): ref T =
# there is no overloaded 'init' here, so we need to state that it's an
# open symbol explicitly:
mixin init
new result
init result
Bind statement
--------------
The ``bind`` statement is the counterpart to the ``mixin`` statement. It
can be used to explicitly declare identifiers that should be bound early (i.e.
the identifiers should be looked up in the scope of the template/generic
definition):
.. code-block:: nim
# Module A
var
lastId = 0
template genId*: expr =
bind lastId
inc(lastId)
lastId
.. code-block:: nim
# Module B
import A
echo genId()
But a ``bind`` is rarely useful because symbol binding from the definition
scope is the default.
|