Define default values for function arguments

67,979

Solution 1

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:

  • Variables could be made to accept more than one name
  • Positional variables and keyword variables can be interspersed - and defining both can give precedence to either (or cause an error)
  • Keyword-only positionless variables can be made, as well as nameless position-only ones
  • The fairly-verbose table construction could be done by parsing a string
  • The argument list could be used verbatim if the function is called with something other than 1 table

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 '#').

Solution 2

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.

Solution 3

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.

Solution 4

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()

Solution 5

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.

Share:
67,979
ripat
Author by

ripat

Airline pilot by day and night. Linux enthusiast the rest of the time. "Why is it that all of the instruments seeking intelligent life in the universe are pointed away from Earth?"

Updated on May 01, 2021

Comments

  • ripat
    ripat about 3 years

    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.