Welcome to Nim.
You are actually touching an advanced part of the Nim type system, the `bind
once` versus `bind many` part.
**bind once vs bind many**
Let's go through that with an example.
proc foo1(x, y: SomeInteger) =
echo "Ran foo"
proc foo2(x: SomeInteger, y: SomeInteger) =
echo "Ran foo"
proc bar[T, U: SomeInteger](x: T, y: U) =
echo "Ran bar"
proc baz(x, y: distinct SomeInteger) =
echo "Ran bar"
let a = int32 10
let b = int64 20
block: # fails
foo1(a, b)
block: # fails
foo2(a, b)
block: # works
bar(a, b)
block: # works
baz(a, b)
Run
In the first case `foo1`, from the signature it is intuitive that `x` and `y`
should be of the same type.
The second case `foo2` is equivalent to `foo1`, for generic instantiation, once
`SomeInteger` has been inferred from the first parameter, it is applied
verywhere else. It is called `bind once`. The main benefit is that both
definition are interchangeable, and it seems to be a good default.
There are 2 ways to ask the compiler to not bind once.
Option 1 via indirection: in the `bar` function, `x` of type `T` which is
instantiated to int32, and `y` of type `U` which is instantiated to int64. The
`[T, U: SomeInteger]` declaration only restrict T and U to be of SomeInteger
but never instantiates `SomeInteger` so it's not bind to any concrete type like
in the 2 previous examples.
Option 2 via explecitly asking `bind many` behaviour: in the `baz` function,
the `distinct` keyword tells the compiler to lock the inference of
`SomeInteger` to the type of `x`.
**The tricky part 1: distinct solution**
Hopefully the examples are simple enough and you managed to follow me until
here. The key part to remember is that `distinct` tells the compiler to not
`bind once`.
You might think, oh I just need to use distinct for my types then.
type
AbstractType = int or string
TestType*[T, Y : distinct AbstractType] = object
Run
Unfortunately, `distinct` has a completely different meaning in a type section.
**``distinct`` types**
Distinct types are well covered in the manual. It allows you to create units
(for example physics units like Meter and Mile or currency like Dollar and
Euro) and prevent you from mixing them at the type system level.
Example
type
Dollar = distinct int
Euro = distinct int
# Let's borrow procedures from the base ``int`` type
proc `+`(x, y: Dollar): Dollar {.borrow.}
proc `+`(x, y: Euro): Euro {.borrow.}
# And overload the print to add the unit
proc `$`(x: Dollar): string =
$int(x) & " dollars"
proc `$`(x: Euro): string =
$int(x) & " euro"
let a = 10.Dollar
let b = 3.Dollar
let c = 5.Euro
let d = 1.Euro
echo a+b # Prints 13 dollars
echo c+d # Prints 6 euros
# Compile-time error "Type mismatch"
# echo a+c
Run
**The tricky part 2: indirection solution**
Since `distinct` doesn't work in a type section. You might think, well I can
use the `bar` solution, bind `T` and `U` to different types.
type
AbstractType = int or string
TestType*[T: AbstractType; Y: AbstractType] = object
a : T
b : Y
proc newTestType[T, Y: AbstractType](a: T, b: Y) : TestType[T, Y] =
return TestType[T, Y](a : a, b : b)
proc print_test_type(t : TestType) =
echo t.a
echo t.b
let
t1 = newTestType(1, 2)
t2 = newTestType("string", "here")
t3 = newTestType(10, "mixed")
echo typedesc(t1)
print_test_type(t1)
echo typedesc(t2)
print_test_type(t2)
echo typedesc(t3)
print_test_type(t3)
Run
Well unfortunately it doesn't work, within a type section, all the generic T, Y
types are `bind once`.
**The solution**
@thenjip nailed it, you need to shake the typesystem a bit until you "bind many
indirections" directly at the type section level instead of the proc level:
type
AbstractType = int or string
TestType*[T : distinct AbstractType | AbstractType; Y : distinct
AbstractType | AbstractType] = object
a : T
b : Y
proc newTestType[T, Y](a: T, b: Y) : TestType[T, Y] =
return TestType[T, Y](a : a, b : b)
proc print_test_type(t : TestType) =
echo t.a
echo t.b
let
t1 = newTestType(1, 2)
t2 = newTestType("string", "here")
t3 = newTestType(10, "mixed")
echo typedesc(t1)
print_test_type(t1)
echo typedesc(t2)
print_test_type(t2)
echo typedesc(t3)
print_test_type(t3)
Run
**Language design**
More for future improvement of the language, I think the `distinct` keyword to
create unit types and the `distinct` keyword for the `bind many` type
inferrence should be separated to improve language ergonomics. We could use
`mixed` instead for the `bind many`
proc baz(x, y: mixed SomeInteger) =
echo "Ran bar"
Run