Reputation: 3236
In the Lua wiki I found a way to define default values for missing arguments:
function myfunction(a,b,c)
b = b or 7
c = c or 5
print (a,b,c)
end
Is that the only way? The PHP style myfunction (a,b=7,c=5)
does not seem to work. Not that the Lua way doesn't work, I am just wondering if this is the only way to do it.
Upvotes: 117
Views: 105680
Reputation: 11171
As always, "Lua gives you the power, you build the mechanisms". The first distinction to make here is that between named parameters and the commonly used parameter list.
Assuming all your args are given in the parameter list as follows, they will all be initialized. At this point, you can't distinguish between "wasn't passed" and "was passed as nil
" - both will simply be nil
. Your options for setting defaults are:
or
operator if you expect a truthy value (not nil
or false
). Defaulting to something even if false
is given might be a feature in this case.nil
check param == nil
, used either as if param == nil then param = default end
or the typical Lua ternary construct param == nil and default or param
.If you find yourself frequently repeating the patterns from point (2), you might want to declare a function:
function default(value, default_value)
if value == nil then return default_value end
return value
end
(whether to use global or local scope for this function is another issue I won't get into here).
I've included all three ways the following example:
function f(x, y, z, w)
x = x or 1
y = y == nil and 2 or y
if z == nil then z = 3 end
w = default(w, 4)
print(x, y, z, w)
end
f()
f(1)
f(1, 2)
f(1, 2, 3)
f(1, 2, 3, 4)
note that this also allows omitting arguments inbetween; trailing nil
arguments will also be treated as absent:
f(nil)
f(nil, 2, 3)
f(nil, 2, nil, 4)
f(1, 2, 3, nil)
A lesser known feature of Lua is the ability to actually determine how many arguments were passed, including the ability to distinguish between explicitly passed nil
arguments and "no argument" through the select
function. Let's rewrite our function using this:
function f(...)
local n_args = select("#", ...) -- number of arguments passed
local x, y, z, w = ...
if n_args < 4 then w = 4 end
if n_args < 3 then z = 3 end
if n_args < 2 then y = 2 end
if n_args < 1 then x = 1 end
print(x, y, z, w)
end
f() -- prints "1 2 3 4"
f(nil) -- prints "nil 2 3 4"
f(1, nil) -- prints "1 nil 3 4"
f(1, nil, 3) -- prints "1 nil 3 4"
f(nil, nil, nil, nil) -- prints 4x nil
Caveat: (1) the argument list got dragged into the function, hurting readability (2) this is rather cumbersome to write manually, and should probably be abstracted away, perhaps time using a wrapper function wrap_defaults({1, 2, 3, 4}, f)
that supplies the defaults as appropriate. Implementation of this is left up to the reader as an exercise (hint: the straightforward way would first collect the args into a garbage table, then unpack that after setting the defaults).
Lua provides syntactic sugar for calling functions with a single table as the only argument: f{...}
is equivalent to f({...})
. Furthermore, {f(...)}
can be used to capture a vararg returned by f
(caveat: if f
returns nil
s, the table will have holes in it's list part).
Tables also allow implementing named "arguments" as table fields: Tables allow mixing a list and a hash part, making f{1, named_arg = 2}
perfectly valid Lua.
In terms of limitations, the advantage of table call is that it only leaves a single argument - the table - on the stack rather than multiple arguments. For recursive functions, this allows hitting the stack overflow later. Since PUC Lua drastically increased the stack limit to ~1M this isn't much of an issue anymore; LuaJIT still has a stack limit of ~65k however, and PUC Lua 5.1 is even lower at around 15k.
In terms of performance & memory consumption, the table call is obviously worse: It requires Lua to build a garbage table, which will then waste memory until the GC gets rid of it. Garbage parameter tables should therefore probably not be used in hotspots where plenty of calls happen. Indexing a hashmap is also obviously slower than getting values straight off the stack.
That said, let's examine the ways to implement defaults for tables:
unpack
(table.unpack
in later versions (5.2+)) can be used to convert a table into a vararg, which can be treated like a parameter list; note however that in Lua the list part can't have trailing nil
values, not allowing you to distinguish "no value" and nil
. Unpacking / destructuring to locals also helps performance since it gets rid of repeated table indexing.
function f(params)
local x, y, z, w = unpack(params)
-- use same code as if x, y, z, w were regular params
end
f{1, 2, nil}
if you use named fields, you'll have to explicitly destructure those:
function f(params)
local x, y, z, w = params.x, params.y, params.z, params.w
-- use same code as if x, y, z, w were regular params
end
f{x = 1, w = 4}
mix & match is possible:
function f(params)
local x, y, z = unpack(params)
local w = params.w
-- use same code as if x, y, z, w were regular params
end
f{1, 2, w = 4}
The __index
metatable field can be used to set a table which is indexed with name
if params.name
is nil
, providing defaults for nil
values. One major drawback of setting a metatable on a passed table is that the passed table's metatable will be lost, perhaps leading to unexpected behavior on the caller's end. You could use getmetatable
and setmetatable
to restore the metatable after you're done operating with the params
, but that would be rather dirty, hence I would recommend against it.
function f(params)
setmetatable(params, {__index = {x = 1, y = 2, z = 3, w = 4}})
-- use params.[xyzw], possibly unpacking / destructuring
end
f{x = 1}
in addition to the presumably garbage params table, this will create (1) a garbage metatable and (2) a garbage default table every time the function is called. This is pretty bad. Since the metatable is constant, simply drag it out of the function, making it an upvalue:
local defaults_metatable = {__index = {x = 1, y = 2, z = 3, w = 4}}
function f(params)
setmetatable(params, defaults_metatable)
-- use params.[xyzw], possibly unpacking / destructuring
end
If you want a default table without the hackyness of metatables, consider once again writing yourself a helper function to complete a table with default values:
local function complete(params, defaults)
for param, default in pairs(defaults) do
if params[param] == nil then
params[param] = default
end
end
end
this will change the params
table, properly setting the defaults; use as params = complete(params, defaults)
. Again, remember to drag the defaults
table out of the function.
Upvotes: 8
Reputation:
Short answer is that it's simplest and best way . in lua , variables by default equal with nil
. this means if we don't pass argument to lua functions ,the argument is exits but is nil
and lua programmers uses of this lua attribute for set the default value .
also it's not a way for set default value but you can use following function
this function create a error is you don't pass values to arguments
function myFn(arg1 , arg2)
err = arg1 and arg2
if not err then error("argument") end
-- or
if not arg1 and arg2 then error("msg") end
but it's not a good way and better is don't use of this function
and in diagrams shows optional argument in [,arg]
function args(a1 [,a2])
-- some
end
function args ( a1 [,a2[,a3]])
-- some
end
Upvotes: 1
Reputation: 3878
If your function expects neither Boolean false
nor nil
to be passed as parameter values, your suggested approach is fine:
function test1(param)
local default = 10
param = param or default
return param
end
--[[
test1(): [10]
test1(nil): [10]
test1(true): [true]
test1(false): [10]
]]
If your function allows Boolean false
, but not nil
, to be passed as the parameter value, you can check for the presence of nil
, as suggested by Stuart P. Bentley, as long as the default value is not Boolean false
:
function test2(param)
local default = 10
param = (param == nil and default) or param
return param
end
--[[
test2(): [10]
test2(nil): [10]
test2(true): [true]
test2(false): [false]
]]
The above approach breaks when the default value is Boolean false
:
function test3(param)
local default = false
param = (param == nil and default) or param
return param
end
--[[
test3(): [nil]
test3(nil): [nil]
test3(true): [true]
test3(false): [false]
]]
Interestingly, reversing the order of the conditional checks does allow Boolean false
to be the default value, and is nominally more performant:
function test4(param)
local default = false
param = param or (param == nil and default)
return param
end
--[[
test4(): [false]
test4(nil): [false]
test4(true): [true]
test4(false): [false]
]]
This approach works for reasons that seem counter-intuitive until further examination, upon which they are discovered to be kind of clever.
If you want default parameters for functions that do allow nil
values to be passed, you'll need to do something even uglier, like using variadic parameters:
function test5(...)
local argN = select('#', ...)
local default = false
local param = default
if argN > 0 then
local args = {...}
param = args[1]
end
return param
end
--[[
test5(): [false]
test5(nil): [nil]
test5(true): [true]
test5(false): [false]
]]
Of course, variadic parameters completely thwart auto-completion and linting of function parameters in functions that use them.
Upvotes: 3
Reputation: 10675
If you want named arguments and default values like PHP or Python, you can call your function with a table constructor:
myfunction{a,b=3,c=2}
(This is seen in many places in Lua, such as the advanced forms of LuaSocket's protocol modules and constructors in IUPLua.)
The function itself could have a signature like this:
function myfunction(t)
setmetatable(t,{__index={b=7, c=5}})
local a, b, c =
t[1] or t.a,
t[2] or t.b,
t[3] or t.c
-- function continues down here...
end
Any values missing from the table of parameters will be taken from the __index
table in its metatable (see the documentation on metatables).
Of course, more advanced parameter styles are possible using table constructors and functions- you can write whatever you need. For example, here is a function that constructs a function that takes named-or-positional argument tables from a table defining the parameter names and default values and a function taking a regular argument list.
As a non-language-level feature, such calls can be changed to provide new behaviors and semantics:
Some useful functions for writing argument translators are unpack
(moving to table.unpack
in 5.2), setfenv
(deprecated in 5.2 with the new _ENV
construction), and select
(which returns a single value from a given argument list, or the length of the list with '#'
).
Upvotes: 112
Reputation: 9549
In my opinion there isn't another way. That's just the Lua mentality: no frills, and except for some syntactic sugar, no redundant ways of doing simple things.
Upvotes: 54
Reputation: 10675
Technically, there's b = b == nil and 7 or b
(which should be used in the case where false
is a valid value as false or 7
evaluates to 7), but that's probably not what you're looking for.
Upvotes: 25
Reputation: 639
The only way i've found so far that makes any sense is to do something like this:
function new(params)
params = params or {}
options = {
name = "Object name"
}
for k,v in pairs(params) do options[k] = v end
some_var = options.name
end
new({ name = "test" })
new()
Upvotes: 10