I am currently trying to find out how I can pass stuff as generic arguments, how resolution of generic types work, and what doesn't work or goes wrong.
So first of all, I can pass integers as generic arguments, when I put a static[] around them.
type MyType[N : static[int]] = array[N, int]
var a : MyType[18]
echo a.N # prints 18
But for some reason I can't do the same for procedures:
proc foo[N : static[int]](): void =
echo N
echo foo[19]() # error type expected
This doesn't mean I can't have values as generic arguments for proceduers, I just need to wrap them in a type:
type IntAsType[N : static[int]] = object
proc bar[T : IntAsType](): void =
echo T.N
bar[IntAsType[13]]() # prints 13
This doesn't mean I am forced to have generic parameters to be all types. I just am not allowed to pass them explicitly:
proc bar2[N](_ : IntAsType[N]): void =
echo N
var temp : IntAsType[9]
# now the generic parameter N is a actually a number (9).
bar2(temp) # prints 9
# but I am still not allowed to explicitly pass the number 9 in the bracket
bar2[9](temp)
# Error: type expected
When I declare a procedure as a generic type, but leave out the arguments to the generic type, it still works, even though I couldn't find documentation that it should. In my observations, a new Symbol is introduced that shares the same name as the Generic type, but it is explicitly instanciated with all generic values.
proc baz(value: MyType): void =
echo MyType.N #MyType hides module.MyType
# var tmp : MyType[12]
# Error: no generic parameters allowed for MyType ( because it is not the generic type anymore
baz(a) # prints 18
when leaving out the generic parameter, different instanciation of the same generic type are not allowed
proc foobar(valueA: MyType, valueB: MyType): void =
echo valueA.N, " ", valueB.N
foobar(a,a) # prints 18 18
foobar(b,b) # prints 19 19
#foobar(a,b) # doesn't work (kind of expected)
proc foobar2[N1,N2](valueA: MyType[N1], valueB: MyType[N2]): void =
echo N1, " ", N2
foobar2(a,a) # prints 18 18
foobar2(b,b) # prints 19 19
foobar2(a,b) # prints 18 19
I am currently testing the limits of passing a static[openarray[int]] to a generic procedure, but it seems like I am in the very experimental zone here.
My goal is to eventually have a Tensor type. A Tensor can be interpreted as a hyper matrix, meaning that it has N dimensions for the data that stores the values.
as I tried out, putting anything more complex is the static area of a generic works in the beginning, but it will always make the compiler explode at some point:
type IntAsType[N : static[int]] = object
proc foobar0[ValueA, ValueB](arg1: IntAsType[ValueA]; arg2: IntAsType[ValueB]): void =
echo "foobar0"
echo ValueA
echo ValueB
echo "foobar0"
var
tmpA0 : IntAsType[7]
tmpB0 : IntAsType[8]
foobar0(tmpA0, tmpB0)
# this is the only case that works, everything else crashes during compilation
type IntSeqAsType[Values : static[seq[int]]] = object
proc foobar1[SeqA, SeqB](arg1: IntSeqAsType[SeqA]; arg2: IntSeqAsType[SeqB]): void =
echo "foobar1"
echo SeqA
echo SeqB
echo "foobar1"
var
tmpA1 : IntSeqAsType[@[1,2,3]]
tmpB1 : IntSeqAsType[@[4,5,6,7]]
foobar1(tmpA1, tmpB1)
# Error: internal error: genLiteral(nkBracket)
# No stack traceback available
# To create a stacktrace, rerun compilation with ./koch temp compile <file>
type IntOpenarrayAsType[Values : static[openarray[int]]] = object
proc foobar2[SeqA, SeqB](arg1: IntOpenarrayAsType[SeqA]; arg2: IntOpenarrayAsType[SeqB]): void =
echo "foobar2"
echo SeqA
echo SeqB
echo "foobar2"
var
tmpA2 : IntOpenarrayAsType[[1,2,3]]
tmpB2 : IntOpenarrayAsType[[4,5,6,7]]
foobar2(tmpA2, tmpB2)
# Error: type mismatch: got (IntOpenarrayAsType[[1, 2, 3]], IntOpenarrayAsType[[4, 5, 6, 7]])
# but expected one of:
# proc foobar2[SeqA, SeqB](arg1: IntOpenarrayAsType[SeqA];
# arg2: IntOpenarrayAsType[SeqB]): void
proc `$`(data : array[3,int]) : string =
result = "["
result &= $data[0]
for i in 1 .. high(data):
result &= ", "
result &= $data[1]
type IntArray3AsType[Values : static[array[3,int]]] = object
proc foobar3[SeqA, SeqB](arg1: IntArray3AsType[SeqA]; arg2: IntArray3AsType[SeqB]): void =
echo "foobar3"
echo SeqA
echo SeqB
echo "foobar3"
var
tmpA3 : IntArray3AsType[[1,2,3]]
tmpB3 : IntArray3AsType[[4,5,6]]
foobar3(tmpA3, tmpB3)
# Error: internal error: genLiteral(nkBracket)
# No stack traceback available
# To create a stacktrace, rerun compilation with ./koch temp compile <file>
It seems like, it is possible to pass more complex types as a generic value. This is what eventually compiled without problems:
proc mkstring(data : openarray[int]; before, sep, after: string) : string =
result = before
result &= $data[0]
for i in 1 .. high(data):
result &= sep
result &= $data[i]
result &= after
type
MyObjectType = object
N: int
values: array[16, int]
MyObjectValueAsType[Value : static[MyObjectType]] = object
proc myObjectType( args: openarray[int] ): MyObjectType =
result.N = args.len
for i in 0 .. high(args):
result.values[i] = args[i]
proc `$`(v: MyObjectType): string =
v.values[0 ..< v.N].mkstring("myObjectType([", ", ", "])")
proc foobar4[ValueA, ValueB](arg1: MyObjectValueAsType[ValueA]; arg2: MyObjectValueAsType[ValueB]): void =
echo "foobar4"
echo ValueA
echo ValueB
echo "foobar4"
var
tmpA4 : MyObjectValueAsType[ myObjectType([1, 2, 3]) ]
tmpB4 : MyObjectValueAsType[ myObjectType([4, 5, 6, 7]) ]
foobar4(tmpA4, tmpB4)
# output:
# foobar4
# myObjectType([1, 2, 3])
# myObjectType([4, 5, 6, 7])
# foobar4
It seems to be that the value that is eventually passed as the generic argument needs to be of an POD (plain old data) type. That means, that the generic argument may not be of any dynamic length, or generic by itself in any possible way.If I understand this correctly,
echo foo[19]() # error type expected
foo[]() is procname[type](parameters)
and foo[17]() isn't valid because 17 is a value not a type
so it should be something like foo[int]()
static[]
A static type is known at compile time
which means they are constants or literals (or ??)
but are NOT variables, so for example:
fooint # is valid
fooint # is not valid
foostring # is valid
foostring # is not valid
All types are known at compile time in these examples, but my example is about passing values in generic parameters, not types. And I want to pass 19 as generic value (not type) to foo, therefore N has the tag : static[int]. The funny thing is, that I am allowed to pass values as generics to functions, I just may not write values in the brackets [] of the function call. If the values are inferred from types of generic arguments, everything is fine. I have an example in there with IntAsType. I think it's totally weird that I have to do that, but hey at least it works.
Take a look at foobar4(tmpA4, tmpB4) tmpA4 and tmpB4 are completely empty objects, there is no value stored in them, but still I can pass array to the function as a compile time constant.
This is how you pass a static param to a proc:
proc foo(N: static[int]): void =
var a: array[N, int]
echo N
echo foo(19)
The proc will be instantiated once for each unique integer value passed to it.
There is no intended requirement that static params should be POD types, but perhaps this is not very well covered in the test suite. Please file bugs if you run into such problems.
@zahary Thanks a lot for your hint. I did not know about that and I think it might be much easier, when I don't need to pass empty objects that wrap the generics in the type.
The question for me still remains, how the compiler determines weather two generic values are the same when they are used defined types. Does it use internally a hash map? Do I need to provide a hash and equals function for my type?