Passing variadic args

I have a function wrap that wraps function fn. fn has arguments with defaults. I wish to avoid having to duplicate the argument defaults in wrap. How do I go about this?

rec {
  wrap = { a, d ? null }: (fn { inherit d; b = a + "-hi"; });
  fn = { b, d ? null }: b + (if d = null then "-meep" else d);
}

This doesn’t work, it fails if d is not passed:

rec {
  wrap = { a, ... } @ args: (fn { d = args.d; b = a + "-hi"; });
  fn = { b, d ? null }: b + (if d = null then "-meep" else d);
}

One option is to write your args like

fn { ${if args?d then "d" else null} = args.d; b = a + "-hi"; }

This works reasonably well if you just need to do this for a single arg.

Another option if you’re just trying to transform one arg and leave the rest alone is to write it that way:

fn (args // { b = a + "-hi"; })

This approach doesn’t work with your example as written though as it will pass an arg a to fn, which isn’t declared and therefore will fail.

A third option if you need to pass along many args but can’t pass along everything is to write something using builtins.functionArgs. In your case builtins.functionArgs fn will return { b = false; d = true; }, which you can intersect with args. Something like

fn (builtins.intersectAttrs (builtins.functionArgs fn) args // { b = args.a + "-hi"; })
2 Likes

I forgot to suggest a fourth option: Where you want wrap to take variadic arguments but you don’t want to allow any args that fn doesn’t take, except for a. In this case you simply remove the extra args you’re accepting from the args set and otherwise pass it along, e.g.

wrap = { a, ... } @ args: fn (builtins.removeAttrs args [ "a" ] // { b = a + "-hi"; })

Admittedly the user can now pass b to wrap and it will be overwritten rather than fail, but at least they can’t pass c:

nix-repl> wrap { a = "oh"; }
"oh-hi-meep"
nix-repl> wrap { a = "oh"; d = "-there"; }
"oh-hi-there"
nix-repl> wrap { a = "oh"; b = "ignored"; }
"oh-hi-meep"
nix-repl> wrap { a = "oh"; c = "wat"; }
error: anonymous function at (string):1:2 called with unexpected argument 'c', at (string):1:21
1 Like