Skip to content

[RFC] guidelines for when to use typedesc vs generics #40

@timotheecour

Description

@timotheecour

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:
    with proc foo[T](a: T) calling foo(1) infers T=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)

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

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

typedesc pros

has advantages of behaving like a regular function parameter, eg:

  • can be used with UFCS, eg: int.foo or with operator syntax foo 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

same for generics and 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 type T 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions