Reputation: 25
I have a proc called:
proc fill(image: Pixels, r, g, b, a: uint8
which needs 4 uint8 values passed to it as parameters to fill a image with a color.
I also have a tuple called green:
let green = (0.uint8, 255.uint8, 0.uint8, 255.uint8)
I wish i could call the proc like this, or similar: image.fill(green)
but that errors with a type mismatch.
Is there a more elegant way than writing ìmage.fill(green[0], green[1], green[2], green[3])
?
Upvotes: 1
Views: 549
Reputation: 7210
If you routinely want to perform this kind of tuple unpacking (like f(*a)
in python / apply
in lisp(s)), you can write some macros for this unpacking (and currying since you have arguments not present in the tuple required for fill
).
unpacking / spread / apply:
macro `..*`(f, t: typed): auto =
var args: seq[NimNode] = @[]
let ty = t.getTypeImpl
for e in ty:
args.add(newDotExpr(t, e[0]))
result = newCall(f, args)
To give you the unpacking; however, you will also need a curry
for doing the equivelent of functools.partial
in python (for the first argument image
)
Currying:
macro curry(f: typed; args: varargs[untyped]): untyped =
let ty = f.getType
let tyi = f.getTypeImpl[0]
assert($ty[0] == "proc", "first param is not a function")
let n_remaining = ty.len - 2 - args.len
assert n_remaining > 0, "cannot curry all the parameters"
var callExpr = newCall(f)
args.copyChildrenTo callExpr
var params: seq[NimNode] = @[]
params.add ty[1]
for i in 0..<n_remaining:
let loc = i+2+args.len
let name = $(tyi[loc - 1][0])
let param = ident(name)
params.add newIdentDefs(param, ty[loc])
callExpr.add param
result = newProc(procType = nnkLambda, params = params, body = callExpr)
proc baz(o: string, r, g, b, a: uint8): string = $g
let green: tuple[r, g, b, a: uint8] = (0.uint8, 255.uint8, 0.uint8, 0.uint8)
echo (baz.curry("example") ..* green)
I do not think you can make a macro that works on the tuple itself rather than both fill
(or the function) and the tuple both because macros can not go this direction "up" in the AST.
Upvotes: 1
Reputation: 21314
Probably the simplest approach would be to wrap the fill()
function in another function that takes a tuple argument. Something like this would work, overloading fill()
to take the new argument types, so long as the return types of fill()
and the overloaded fill()
match:
proc fill(image: Pixels, color: tuple[r, g, b, a: uint8]): void =
fill(image, color.r, color.g, color.b, color.a)
Then calling fill()
with the tuple argument will call the appropriate version:
myImage.fill(green)
Upvotes: 3