-
Notifications
You must be signed in to change notification settings - Fork 22
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
$awherever 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
resultnote
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
resultproposal 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