Description
API's like $(a: MyType)
(which are what we almost exclusively use in stdlib, nimble etc) do not compose efficiently because they create temporaries, allocate etc.
instead, we should build API's around appending. This gives best of both worlds:
- efficiency where needed
- still possible to use old way
$a
wherever it's convenient, thanks a single unary$
overload:
template `$`*[T](a: T): string =
## the only unary `$` that's ever needed
var result: string
result$a
result
note
outplace
operator is not relevant to this discussion, we don't want to use outplace operator for something so common like stringification
proposal part 1: binary $ operator
the API to stringify MyType
shall be:
proc `$`(result: var string, a: MyType)
example
import system except `$`
## overloads of binary `$` for all types we need to support
proc `$`*(result: var string, a: float) = result.addFloat a
proc `$`*(result: var string, a: int) = result.addInt a
template `$`*(result: var string, a: string) = result.add a
template `$`*[T](a: T): string =
## the only unary `$` that's ever needed
var result: string
result$a
result
proposal part 2: variadic strAppend
the following variadic strAppend
is always preferable to &
+ $
, and often preferable to varargs[string,$]
following macro strAppend
advantageously replaces many patterns, yet is as efficient as can be.
In particular, it allows writing a more efficient echo(a: varargs[string, $])
and write(a: File, args: varargs[string, $])
, without introducing a bunch of temporaries.
import macros
macro strAppend*(dst: var string, args: varargs[untyped]): void =
result = newStmtList()
for ai in args:
result.add quote do:
`dst`$`ai`
macro strAppend*(args: varargs[untyped]): string =
result = newStmtList()
let dst = genSym(nskVar, "dst")
result.add quote do:
var `dst`: string
for ai in args:
result.add quote do:
`dst`$`ai`
result.add quote do:
`dst`
these advantageously replace varargs[string,$] versions of system.echo, system.write:
template echo*(args: varargs[untyped]) =
## more efficient than system.echo: no temporaries
## could be further optimized using a fixed size buffer when short enough or even via a single threadvar string to do all the appending
system.write(stdout, strAppend(args, "\n"))
template write*(f: File, a: varargs[untyped]) =
## more efficient than `write*(f: File, a: varargs[string, `$`]` in system/io
system.write(f, strAppend(a))
usage
proc main()=
var x = 0.3
var ret: string
ret$x
ret$" bar "
ret$12
doAssert ret == "0.3 bar 12"
doAssert $0.3 == "0.3"
var ret2: string
ret2.strAppend 0.1, " foo ", 12
doAssert ret2 == "0.1 foo 12"
doAssert strAppend(0.1, " foo ", 12) == "0.1 foo 12"
echo 0.1, " foo ", 12 # prints `0.1 foo 12`
stdout.write 0.1, " to_stdout ", 12, "\n" # prints 0.1 to_stdout 12
main()
note
I have a WIP that would allow writing strAppend
as a template instead of a macro, to avoid depending on macros.nim; in particular that means that strAppend
and echo
thus defined could be in system.nim (or alternatively for system.nim minimalists, hiding strAppend there, using it to redefine echo, and redefining strAppend in some other module, but IMO it just belongs in system since its ubiquitous, replacing "foo" & $bar
both performance wise and syntactically, using less operator noise (unlike both using & and $)).
the way it works is it allows templates to iterate over a varargs.
links
- when done, remove compiler/strutils2