So recently, I've been encountering the case where I want to use varargs of a given type for my API but use the varargs[A, toA] call to an impl proc.
To put it simply with numerical type, I need this :
proc bar(args: varargs[int, toInt]) =
echo args
proc foo*(args: varargs[float]) =
bar(args)
Except varargs[float] cannot be converted varargs[int, toInt] and thus I'm stuck with exposing an API with varargs[int, toInt]
Is there a workaround to this limitation ?
i think your example might be a bit oversimplified, i feel like i'm not getting the full picture. my confusion stems from the fact that varargs[int,toInt] seems more like the api you expose, as it enables the user to mix types in their calls: foo(3.5,9) but i don't know what you're up to.
you could do the conversion by hand. in this case bar could also be changed to varargs[int] or seq[int] if that's useful.
import sequtils
proc foo*(args:varargs[float]) = bar(args.mapIt(it.toInt))
I'll try to give an example closer to what I'm trying to do :)
I have mapped a C type called jl_value_t* that can hold any type in the C API of Julia to JlValue in Nim. I have procedure to convert Nim type to Julia type :
proc nimValToJlVal[T](x: T): JlValue = #...
Now I can have a simpler way of calling Julia function without having to explicitly call the converter and go through temporary variable (the compiler does it for me) :
proc jlCall(funcname: string, args: varargs[JlValue, nimValToJlVal]) =
juliaEval(funcname, args)
Now there is a Julia function that is used a lot, - let's call it dummy_func - so I want to wrap it to make a more idiomatic API. This function can only accept integer and I want to reflect that on the Nim API :
proc julia_dummy_func(args: varargs[int]) =
jlCall("dummy_function", args) # Error can't convert varargs[int] to varargs[JlValue]
I could do args.map(x =>nimValToJlVal(x)), but AFAIK, this would create a copy and I'd like to avoid making unnecessary copy.
thank you for the explanation, clearly the same as your first post but somehow much easier to wrap my head around :)
how about:
import macros
template foo*(args:varargs[float]) =
unpackVarargs(bar,args)
i tried it, and because macros rudely ignore whether procs are exported or not,
import api
foo(3.5,3.9)
compiles and runs even if bar and toInt aren't exported. unpackVarargs is neat but it prevents using additional arguments after, it seems ?
Another example where I was needing this pattern to see why :
func catImpl(tensorargs: varargs[RawTensor, convertRawTensor], axis: int64): RawTensor =
let tensors : ArrayRef[RawTensor] = tensorargs.asTorchView()
rawtensors.cat(tensors, axis)
template cat*[T](tensorargs: varargs[Tensor[T]], axis: int64): Tensor[T] =
convertTensor[T](
unpackVarargs(catImpl, tensorargs, axis)
)
But perhaps I have too many requirements and this will have to do
i had no idea you could use varargs not in the last position. that's pretty cool.
well, you can keep writing new unpackVarargs for the different use cases, this one would have to change the api
import macros:
macro unpackVarargs_last(callee, arg_last: untyped; args: varargs[untyped]):untyped =
result = newCall(callee)
for a in args:
result.add a
result.add arg_last
proc catImpl(args: varargs[int, toInt], axis: int) = discard
template cat*(args: varargs[float], axis: int) = unpackVarargs_last(catImpl,axis,args)
or change catImpl so that the varargs are at the end, and use the unpackVarargs(callee, arg1, args) version.