-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cannot mix generic with typedesc #11152
Comments
@zah I heared that you had a lot of influence on design of typedesc as it currently is. What do you think goes wrong here, and how would be a clean way to fix it. |
Some observations: It works if there is connection between the typedesc argument and the generic argument, but it still depends on how the proc is called: type
Generic[T] = object
proc f[T](X: typedesc[Generic[T]]) = discard
Generic[int].f() # works
f(Generic[int]) # works
f[int](Generic[int]) # fails, probably should work |
I would assume it has something to do with the hidden generic argument when you use Here is an example: type
Vec4[T] = object
data: array[0..3, T]
# this doesn't look generic, but it is
proc foo(arg: Vec4): Vec4 =
result.data[0] = arg.data[0] + 2
result.data[1] = arg.data[1] - 2
result.data[2] = arg.data[2] * 2
result.data[3] = 2
var tmp = Vec4[int](data: [1,2,3,4])
tmp = foo(tmp)
echo tmp
# when you now think you set a single generic argument, you actually
# create a second generic argument.
proc bar[T](arg: Vec4): Vec4 =
echo T
result.data[0] = arg.data[0] + 2
result.data[1] = arg.data[1] - 2
result.data[2] = arg.data[2] * 2
result.data[3] = 2
# cannot instantiate: 'bar[float32]'; got 1 type(s) but expected 2
#let z = bar[float32](tmp)
# now when you actually pass two generic parameters, it still fails,
# but this time without explanation:
# cannot instantiate: 'bar[float32, int]'
#let z = bar[float32, int](tmp)
# if you avoid these hidden generics, then you don't have any problems:
proc baz[T,U](arg: Vec4[U]): Vec4[U] =
echo T
result.data[0] = arg.data[0] + 2
result.data[1] = arg.data[1] - 2
result.data[2] = arg.data[2] * 2
result.data[3] = 2
# this works as you expect.
let z = baz[float32, int](tmp)
# as I explained before typedesc has a hidden generic as well, but
# when I try to apply this trick here, it get's even worse:
proc f[T,U](t: typedesc[U]) =
echo "test"
# ``string`` is implicitly converted to ``typedesc[string]``, so this
# should work, but this is the error:
# cannot instantiate: 'f[int, string]'; got 2 type(s) but expected 3
f[int,string](string) BTW: These workarounds do work: proc f1[T,X](t1: typedesc[T]; t2: typedesc[X]) =
echo "test"
f1(int, string)
proc f2[T,X]() =
echo "test"
f2[int, string]() |
Yes, this is a more general problem with explicit generic instantiation. Implicit generics create hidden parameters and the instantiation logic doesn't try to compensate for this, neither it allows partial instantiation as supported in C++. For this reason, I usually recommend creating APIs that don't rely on explicit generic parameters. I've written more about this here: When your API requires non-inferred types to be passed explicitly, just use proc f(T, X: typedesc) =
echo "test" |
@zah In fact the reason I was mixing them was because I was following your object construction RFC nim-lang/RFCs#48 which seems to have problems with the explicit form |
If you follow my proposal, you would implement the proc init[K, V](T: type Table[K, V]) = initTable[K, V]() This is slightly better, because it allows you to create an alias for the table type: type
UserMap = Table[UserId, User]
...
var m = UserMap.init |
@zah I agree the pattern for type
Vec4[T] = object
data: array[0..3, T]
# two arguments, implicitly generic.
proc foo(arg1, arg2: Vec4) =
discard
var v1: Vec4[float32]
var v2: Vec4[int32]
foo(v1,v2) # does not not work because they are not the same instance of Vec4
# two arguments, implicity generic. But "special"
proc f(T, X: typedesc) =
echo "test"
f(int, string) # does work even though they are not the same instace of typedesc This is far too much special treatment of |
Actually the "specialness" is because in the second case, the types passed to typedesc arguments are "values" similar to how passing different As @Araq, often repeated, there is a mix of types as types and types as values in the typedesc/generics/static handling that makes this area of the compiler the most tricky and bug prone. Also, static, concepts (and typedesc?) were bolted on the generics codepath very late so I guess the design is poor and in great need of a refactor. All in on all, I think we will have to pay the technical debt at one point because the current generics/static/typedesc are creating recurrent issues ({.this.}, strformat, async nested in generics - #8677). They are also quite hard to handle in macros nim-lang/RFCs#44, and triggered lots of discussion about container/generics initialization and constructor API:
All the way up to generic generics/higher kinded types nim-lang/RFCs#5 I'm pretty sure this rework could also benefits concepts. Given the scope, I think this should target 1.X rather than 1.0, the status quo is OK for the short term (but probably not in the long term). |
yes they are value, but values of different types, static:
var t1: typedesc[int]
var t2: typedesc[int]
var t3: typedesc[float]
t1 = t2 # ok
t1 = t3 # type mismatch: got <type float> but expected 'type int' typedesc[float] isn't just a different value of typedesc, like 1 and 2 are different values of |
In your original example they are values of the typedesc type: i.e. proc f(T, X: typedesc) and not proc f(T: typedesc[int], X: typedesc[string])
# or
proc f[U](T: typedesc[U], X: typedesc[U]) |
|
In general, Nim allows you to define both "bind once" type classes and "bind many" type classes as explained the type classes section of the manual. It's up to the standard library to decide which (if any) of the standard types will get the bind-many treatment. Notable examples for this are |
This is all I can find in the type classes section:
I would not call this "explained" it is barely mentioned. There is no example of bind many outside of the concepts section.
Where in the standard library is declared that |
In the initial versions of the implicit generics, it used to be the case that type any* = distinct auto This reflects the time when the manual was written. It would be my preference as well that we use such explicit distinct non-magic types to define BTW, whether the type is considered bind-once or bind-many is relatively easy to fix - it all happens in the |
I just ran into a problem when mixing generics and typedesc: import macros
macro foo(name: string, n1: untyped) =
echo "one"
macro foo(name: string, n1: typedesc, n2: untyped) =
echo "two"
foo("hello"):
a = b I'd expect Is this problem related to this issue? |
@zevv no, yours is just the old |
We really should restrict untyped macros and templates, so that they may not be overloaded. Overloading with both typed and untyped never really worked. |
The error message should be improved, but sometimes in the far future I'd like to see untyped overloading possible. Anyway, untyped overloading should be discussed in the corresponding issue: #9414 |
@mratsim: The problem is the following, when you have a proc with untyped argument, then you tell the compiler, please don't semcheck this AST. When you have another overload that needs this argument to be semchecked, you have a conflict. You have an AST that needs to be both semchecked and not semchecked at the same time. This can only cause problems and should be reported to the programmer as a compilation error. |
Araq already explained on IRC there are certain subleties involved, but what confused me that the compiler fails to differentiate between the two macros, even though they have a different number of arguments. This does work for two macros with only
|
I would have expected that mixing generics and typedescs is possible.
Example
Current Output
Expected Output
Should compile.
Additional Information
The text was updated successfully, but these errors were encountered: