sebastian
sebastian

Reputation: 25

Pass values of tuple as prarameters to proc

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

Answers (2)

modesitt
modesitt

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

ad absurdum
ad absurdum

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

Related Questions