CARVIEW |
Navigation Menu
-
Notifications
You must be signed in to change notification settings - Fork 23
Description
This issue keeps popping up in different places (forum, PR's, issues) [1]. Let's centralize discussion in one place specific to this question instead of scattering the discussion.
Choosing between typedesc vs generics affects how we use the APIs so having guidelines is good.
here's my understanding of what guidelines should be.
I'll keep this top-level post up to date w comments below
generics pros
- supports IFTI (implicit function type instantiation), aka generic type deduction eg:
withproc foo[T](a: T)
callingfoo(1)
infersT=int
there is no such notion with typedesc, it wouldn't make sense (cf mentioned in nim-lang/Nim#3502 (comment))
- makes clear distinction for callers bw runtime args (or static[T] compile time values) and compile time types, eg:
parse[types,...](runtime_args,...)
#or
first_arg.parse[types,...](runtime_args,...)
- can instantiate to create a function:
let foo_inst=foo10[int]
echo foo_inst(10)
echo foo_inst.type.name
that would not be possible with typedesc (short of resorting to lambda etc)
-
can potentially (with language support) be used with a shorthand for the common case of single template calls, as suggested here New unambiguous generics syntax (generic arguments syntax) Nim#3502 (comment) :
a.foo!int
(or whatever other symbol that would make it un-ambiguous) -
consistency/familiarity: that's how most (all?) languages with metaprogramming support work as mentioned here: https://github.com/nim-lang/Nim/issues/7430#issuecomment-379196859
D: void foo(T:U)()
C++ : template<typename T> class fun
and template<> class fun<int>
and std::enable_if for more complex constraints
C#: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters:
public class foo<T> where T : U
swift: https://medium.com/developermind/generics-in-swift-4-4f802cd6f53c func foo<T: U>()
generics cons
- call syntax
foo[T](x)
ambiguous (generic or array subscript depending onfoo
) , cf New unambiguous generics syntax (generic arguments syntax) Nim#3502
NOTE: IIUC, the ambiguity
mentioned is only ambiguous for the lexer (or syntax highlight), not for the compiler
NOTE: this can be fixed by adding a syntax, eg some!(T)(x)
as in D (nim-lang/Nim#3502 (comment)) or foo[:T](x)
(nim-lang/Nim#3502 (comment)); unclear whether this will ever be changed though (lots of code would need to upgrade) EDIT foo[:T](x)
is now implemented
-
EDIT no support for partial generic deduction yet (but see https://github.com/nim-lang/Nim/issues/7529 [RFC] partial generic deduction), which makes currently
input.to[T]
impossible, whereasinput.to(T)
works -
EDIT can't be used with default values, tracked here https://github.com/nim-lang/Nim/issues/7516
typedesc pros
has advantages of behaving like a regular function parameter, eg:
- can be used with UFCS, eg:
int.foo
or with operator syntaxfoo int
; with generics this would not make sense - can be used with named parameter, eg:
proc foo8(T1:typedesc, T2:typedesc): auto = return "T1:" & T1.name & " T2:" & T2.name
echo foo8(int,string)
echo foo8(T2=int, T1=string)
typedesc cons
Some programmers could probably have trouble remembering whether if it was read(int, s) or read(s, int). readint adds the flavor of knowing which part is the compile time arguments and which part is the method arguments
-
can be hard to tell whether an argument is a type or a value, eg:
foo(foo.type, bar)
or worse -
EDIT can't be used at compile time, unlike generics, see typedesc[T] can't be used at compile time, unlike generics Nim#8486 ; however, a workaround is available via an intermediate generic, see that issue
same for generics and typedesc
- both a compile-time only thing (cf Add ByteStream and generic
read
proc to streams Nim#7481 (comment)) - both can be specialized / overloaded, eg proc
proc foo[T:int|float]()
orproc foo(T: typedesc[int|float])
(New unambiguous generics syntax (generic arguments syntax) Nim#3502 (comment)) - both can be used with concepts, eg:
proc foo[T:isSomeConcept]()
orproc foo(T: typedesc[isSomeConcept])
* neither can be used with default values, but see https://github.com/nim-lang/Nim/issues/7516, [RFC] default type parameter for generics and typedesc parameters #7516this is now fixed for typedesc
recommendations ASSUMING https://github.com/nim-lang/Nim/issues/7529 is going to be fixed in near future
- fix ASAP New unambiguous generics syntax (generic arguments syntax) Nim#3502 (ambiguous syntax) eg with @Araq's
foo[:T](x)
syntax or D's foo!(T)(x); if possible add (nonambiguous) shorthand for common case of single template param (eg x.too!T or whatever's nonambiguous) - when IFTI is needed (ie when type deduction from params is needed), use generic
* when typeT
is an output type (not an input type), use generic, eg:input.to[T]
- EDIT: else, use typedesc
question: curious whether there are cases where typedesc is truly preferred? (besides syntactic niceties of allowing UFCS and named parameters)
EDIT /cc @dom96
proposal: CT_typedesc (pending https://github.com/nim-lang/Nim/issues/7529) /
when a function is compile time only (eg no runtime parameters AND can be computed at compile time), use typedesc
. In all other cases, use generics
. Eg:
## property-like proc
proc bar(typedec:T):auto=T.name
## void return proc (eg checks)
proc someStaticCheck(typedec:T) = T.name == "foo"
## compile time values are ok
proc getNth(typedec:T, n: static[int]): auto = T.name[n]
[1] examples of recent discussions on typedesc vs generics:
nim-lang/Nim#3502 (comment)
https://github.com/nim-lang/Nim/issues/7430#issuecomment-377412261
nim-lang/Nim#7481 (comment)
EDIT without such guidelines we end up with having both kinds of functions, eg nim-lang/Nim#7512 Add none[T]() as alias to none(T)