type TVec* {.pure, final.}[T; N: static[int]] = object
vec: array[0..N-1, T]
proc vec2*[T](ary: array[0..1, T]): TVec[T,2] {.inline.} =
result.vec = ary
template defineVectorScalarMultiply(n: expr) {.immediate.} =
proc `*`*[Scalar](v: TVec[Scalar,n], c: Scalar): TVec[Scalar,n] {.inline.} =
for i in 0..(n-1):
result.vec[i] = `*`(v.vec[i], c)
defineVectorScalarMultiply(2)
defineVectorScalarMultiply(3)
when isMainModule:
import unittest
test "vector-scalar arithmetic operators":
let a = vec2([1.0, 2.0])
let b = a * 4.0
But the compiler emits the following error
tmp.nim(18, 2) Info: instantiation from here tmp.nim(20, 14) Error: ambiguous call; both tmp.*(v: TVec[Scalar, static[int literal(3)](3)], c: Scalar): TVec[Scalar, static[int literal(3)](3)] and tmp.*(v: TVec[Scalar, static[int literal(2)](2)], c: Scalar): TVec[Scalar, static[int literal(2)](2)] match for: (TVec[float, static[int literal(2)](2)], float)
If I comment out "defineVectorScalarMultiply(3)", then it works just fine. I expect TVec[float,2] to be a distinct type from TVec[float,3], so I don't understand what's going on. Help?
As a workaround for things that generics cannot currently do, you can also use Nim's include statement to approximate ML functors. For example, to define B-trees, you could do something like this:
import btree.basics # defines ordLessThan, ordGreaterThan, and ordEqual
const N* = 3
type
Key* = string
Value* = string
proc normalize*(key: Key): Key =
toLower(key)
proc compare*(a, b: Key): Ordering =
let c = cmp(a.normalize, b.normalize)
if c < 0: ordLessThan
elif c > 0: ordGreaterThan
else: ordEqual
include btree.implementation # provides implementation based on N,
# Key, Value, normalize, and compare
The above can be made more reusable and cleaner (so that namespaces don't get mixed up) by putting the parameters in a module of its own, say btree3nocaseparams, then do:
import btree3nocaseparams as btreeparams
include btree.implementation
The above should also make the comparison to ML functors (modules parameterized by other modules) clear.
This approach is useful not only when you need to parameterize a module by different functions (case-insensitive vs. case-sensitive comparison, for example) and constants, but also if you need global constants (such as the identity matrix) or variables (such as caches) that cannot be done with normal generics.
I'll add that it would be neat to have this as a first-class language feature so that one doesn't have to write two-line modules for these cases and one could do, e.g.:
# module btree.implementation
from generic params import N, Key, Value, normalize, compare
Which is then used via:
import btree.implementation[params=btree3nocaseparams] as btree3nocase
import generic params as btree3nocaseparams
First, this is not something that Nim actually supports. It would be a hypothetical syntax (for existing functionality).
Second, these are two files. The first one is a module parameterized by another module (by params, to be precise). params is a formal parameter, not describing any actual module. The second file imports the first, setting params to btree3nocase3params for this case). So, no, a statement like:
import generic params as btree3nocaseparams
wouldn't make sense, since the module doing such a generic import would not know of any specific instantiations.