nim-lang / RFCs

A repository for your Nim proposals.
137 stars 23 forks source link

Improve overload resolution for generics and templates #346

Open al6x opened 3 years ago

al6x commented 3 years ago

When multiple routines match the call - Nim tries to find the proc with the best match.

It could be improved, explicit type should have priority over generic type and generic over untyped.

func somefn*(v: int) = discard
func somefn*[T: SomeInteger](v: T) = discard
func somefn*[T](v: T) = discard
template somefn*(v: untyped) = discard

With this improvement it would be possible to combine generic and non generic procs and templates. And it should be a cheap check and not slow down the compilation.

Example:

playground

import sugar, algorithm, sequtils

func sortBy*[T, C](list: openarray[T], op: (T) -> C): seq[T] = list.sortedByIt(op(it))
template sortBy*[T](list: openarray[T], key: untyped): seq[T] = list.sortedByIt(it.key)

let people = @[(name: "Sarah"), (name: "Jim")]
echo people.sortBy((v) => v.name)
echo people.sortBy(name)

Details about Overloading resolution in Manual.

timotheecour commented 3 years ago

I have a better proposal: untyped(expr), see https://github.com/timotheecour/Nim/issues/630

let people = @[(name: "Sarah"), (name: "Jim")]
echo people.sortBy((v) => v.name)
echo people.sortBy(name) # CT error
echo people.sortBy(name.untyped) # ok

explicit is better than implicit when it comes to untyped, and avoids many issues, eg if name is actually typed (eg due to an import) but user intended it to be used as untyped.

name.untyped (or untyped(name)) avoids this ambiguity by making this explicit.

See also https://github.com/nim-lang/Nim/pull/17196 which will reduce need for untyped, but both iterable[T] and untyped(expr) are useful and work well together.

Araq commented 3 years ago

We can easily do both, it's certainly bad that untyped matches as well as a generic T.

al6x commented 3 years ago

One more use case, currently it's impossible to override find to accept proc, with this change it should work.

func find[T](list: openarray[T], check: (T) -> bool, start = 0): int =
  if start <= (list.len - 1):
    for i in start..(list.len - 1):
      if check(list[i]): return i
  -1

assert @[(name: "Jim")].find((u) => u.name == "Jim") == 0