Named Parameters |
|
local function pipe(input, output) output:write(input:read'*a') end pipe(io.stdin, io.stdout)
may be written in a named parameter style as
local function pipe(t) local input = assert(t.input) local output = assert(t.output) output:write(input:read'*a') end pipe({input=io.stdin, output=io.stdout}) pipe{input=io.stdin, output=io.stdout} -- the following shorter syntax is equivalent and preferrred
Optionally, some type of argument checking is performed, which in the most rudimentary form involves the assert
's above must could get more complicated like StructuralPatternMatching. It may be possible for a function to support both named and positional calling conventions:
local function pipe(...) local input, output if type(...) == 'table' then local t = ... input, output = assert(t.input or t[1]), assert(t.output or t[2]) else input, output = ... end output:write(input:read'*a') end pipe(io.stdin, io.stdout) -- positional form pipe{input=io.stdin, output=io.stdout} -- named form pipe{io.stdin, output=io.stdout} -- mixed form (positional and named) pipe{io.stdin, io.stdout} -- mixed form (using only positional)
Optional/default parameters can also be supported via
input, output = assert(t.input or t[1] or io.stdin), assert(t.output or t[2] or io.stdout)
To avoid ambiguity, the above assumes, however, that the first positional parameter can never be a table, or at least that if it is a table then it can be distinguished from a table of named parameters.
The programmer must remember to change the parenthesis from '()' to '{}' when using the named or mixed parameter forms.
One disadvantage of the named form is that the table construction does have some amount of overhead due to the table allocation. Benchmarking can determine how significant this is in practice. In a tight computationally intensive loop, it might be an issue.
Error handling in the named form is also somewhat more complicated and the code more verbose/ugly, but the same issues occur if a positional parameter has a nested structure that must be checked.
The named form does not preserve trailing nil
's because trailing nil
's are ignored in table constructors. Therefore, the following calls are indistinguishable:
f{a,b,nil,d=d}
f{a,b,d=d}
Here's some other, less conventional, possible ways to do named parameters:
f(a, b, {c=c}) f('a',a, 'b',b, 'c',c) -- note: Perl supports a syntactic sugar for this: `a=>$a` is `'a',$a` f(params().a(a).b(b).c(c)) -- C++ chained method call style. f('c,b,a', c,b,a) -- note: parameter resolution can be memoized (see link below) f'c=$(c),b=$(b),a=$(a)' -- variant of above with StringInterpolation [1] syntax sugar for f('c=$(c),b=$(b),a=$(a)',c,b,c)
sum(z=3*i, y=2*i, i)
syntactic sugar for sum['z,y'](3*i, 2*i, i)
so that the named parameter resolution can be memoized. Effects of named parameters on LuaJit optimizations are also explored.