Default values in function argument declaration

Right now to describe a default value you have to do the following:

function foo(bar, baz, baq)
   bar = bar or 1
   baz = baz or {}
   baq = baq or "word"
   -- ...
end

But if luau supported default values for arguments then this would be much easier to read and write:

function foo(bar=1, baz={}, baq="word")
   -- ...
end

In terms of scope, each argument simply gets evaluated within the function environment in order as though it was being assigned on a line at the beginning of the function.

17 Likes

Would these be equivalent?

local function f(b:bool)
	b = b or true -- same as b = true
end
local function g(b:bool=true)
	-- b = true?, or if false is passed then is b false?
end

How can you pass a value to the function that would indicate that you want to pass nil or some other value that would get interpreted as wanting the default value?

local function h(x=1)print(x)end
h() -- ok, this prints 1
h(nil) -- should this print 1 or nil?
h(false) -- should this print 1 or false?

Do the default values get evaluated for each call, or do they get evaluated once and cached (perhaps during the creation of the function)?
If they are evaluated for each call, do they get evaluated when not needed?

These scoping rules would be difficult to parse (the beginning of a function can see all arguments):

--                        ↓ referenced before definition
local function p(x:number=y,y:number)
end

Something similar to GNU C forward parameter declaration could be used to make this simpler:

--       forward ↓ declaration     ↓ makes this less difficult to parse
local function p(y:number;x:number=y,y:number)
end

Where a forward declaration is a non empty comma separated list of names or ellipses which may be followed by a type (but not a default value), followed by a semicolon. Any number of forward declarations may appear before the argument list. The type of a parameter or variable arguments or declaration of a parameter or variable arguments may be inferred from a previous declaration.

local function q(
	a:string,b,b; -- first forward declaration declaring a as string and b as any
	              -- the second b has no effect, the type any is inferred but
	              -- makes no change because it is any by default
	c:number,...:number; -- second forward declaration declaring c as a string
	              -- and ... as number
	c; -- third forward declaration,
	   -- the type is inferred from the previous declaration of c
	...,...; -- fourth forward declaration,
	         -- the types are inferred from the previous declaration of ...
	a:string="a", -- default value is "a", type is provided redundantly
	b=c, -- references the next argument as a default
	c=..., -- default value is the first value from the variable arguments,
	       -- type is inferred from previous declarations
	... -- variable arguments may have no default value
	    -- type is inferred from previous declarations
	)
end

Forward declarations may declare a variable multiple times. A declared variable must be included in the parameter list. The types of all declarations of a parameter must match the type of the parameter. The same rules apply to variable arguments, there may be multiple declarations of variable arguments, the declarations of a variable arguments must match the type of the variable arguments, and if variable arguments are not included as a parameter then no declaration for variable arguments may be included. Two types match if they are equivalent.

I don’t see anything difficult about this. y isn’t defined yet, so it is nil. Things happen in order. Each default just executes “if this value is undefined, evaluate the default and set the argument” in order within the function scope.

Forward declarations are bad language design imo. If you care about flow control during argument defaults then just put your logic in the function body. At that point the x = x or is negligible compared to what you’re actually writing.

The point of this suggestion is to eliminate frequently-rewritten boilerplate code at the top of every function, not to allow people to execute arbitrarily complicated logical operations within the function arguments.

4 Likes