nim-lang / RFCs

A repository for your Nim proposals.
135 stars 26 forks source link

Converted parameters with `to[T, toT]` types #549

Open xjzh123 opened 5 months ago

xjzh123 commented 5 months ago

Abstract

A proc can declare that some certain parameter of it can take arguments of different types and convert them to certain types.

Motivation

Sometimes converters are used to simplify calling some routines, like here. But now that converters are global, they sometimes cause troubles, like here, leading to ambigious calls. Now that converters are frequently used only for some specific routines, why not make routines, instead of types, have converters? We can already do similar things with generic, auto type, when and overloading, but this RFC can make it simpler to write such a conversion.

Description

Parameters can have "converters" (not Nim converters). This is like varargs, which can already convert arguments to certain types. Like varargs, we can make this a special type, to:

proc foo(x: to[int]): int =
  2 * x

# This is the same
proc foo(x: auto): int =
  let x = int(x)
  2 * x

# This is also the same
proc foo[T](x: T): int =
  let x = int(x)
  2 * x

It is also useful to convert with certain routines:

proc toAddr[I; T](a: array[I, T]): ptr T = addr(a[0])

proc useAddr[T](p: to[ptr T, toAddr]) =
  discard # here p is ptr T

Like varargs, overloaded procs are used on demand:

# StringBuilder is an imaginary object type
proc add(x: StringBuilder, s: to[string, `$`]) =
  x.data &= s # if s is T, s is converted to string with (proc (T): string)`$`

If this proposal has downsides, I think:

  1. The semantic is not that readable. But with varargs already existing, it is still easy to understand.
  2. It still requires editing every proc
  3. It is easily replaced by existing syntax.

Code Examples

No response

Backwards Compatibility

No response

ZoomRmc commented 5 months ago

This is already achievable with concepts:

type ToInt = concept x
  int(x) is int

proc foo(x: ToInt): int = 2 * x

doAssert foo(2.Natural) == 4

###
type
  ToString = concept x
    string(x) is string
  Stringlet = distinct string

proc add(x: var string; s: ToString) =
  x &= string(s)

var s: string = "what follows was "
s.add("not a string".Stringlet)

doAssert s == "what follows was not a string"
xjzh123 commented 5 months ago

This is already achievable with concepts:

type ToInt = concept x
  int(x) is int

proc foo(x: ToInt): int = 2 * x

doAssert foo(2.Natural) == 4

###
type
  ToString = concept x
    string(x) is string
  Stringlet = distinct string

proc add(x: var string; s: ToString) =
  x &= string(s)

var s: string = "what follows was "
s.add("not a string".Stringlet)

doAssert s == "what follows was not a string"

Ahh! idk that. fancy

xjzh123 commented 1 month ago

This is already achievable with concepts:

type ToInt = concept x
  int(x) is int

proc foo(x: ToInt): int = 2 * x

doAssert foo(2.Natural) == 4

###
type
  ToString = concept x
    string(x) is string
  Stringlet = distinct string

proc add(x: var string; s: ToString) =
  x &= string(s)

var s: string = "what follows was "
s.add("not a string".Stringlet)

doAssert s == "what follows was not a string"

Wait, I just discovered that you are doing explicit converting with string(s). You don't need to do it with Natural just because Natural already supports being multiplied with ints. Well... I know you can do explicit converting, but to[T, toT] will let you write less code...

hugosenari commented 1 month ago

Related #168

arnetheduck commented 1 month ago

. You don't need to do it with Natural just because Natural already supports being multiplied with ints.

this is also why we recommend to never use Natural as input parameters (or indeed, any range type) in production code since it performs an implicit narrowing conversion - an expression like let x = -1; let y = Natural(x) will panic at runtime, as would the code in this proposal if the conversions happen to be narrowing.

Indeed, what this RFC lacks a way to deal with narrowing conversions safely - these are more common than one would thing, ie string-to-int can obviously fail, as can many other common ones - in other words, for this to fly, it would ideally be constrained to non-narrowing conversions (ie conversions that don't panic or raise exceptions)