[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Proposal: Function Definition Named/Default Argument Syntax Sugar
- From: Andrew Starks <andrew.starks@...>
- Date: Wed, 13 Jun 2012 15:51:13 -0500
On Wed, Jun 13, 2012 at 12:39 PM, Dirk Laurie <dirk.laurie@gmail.com> wrote:
<snip>
> 1. Defining a function with {...} instead of (...) means that it takes
> a single table-valued argument, with the specified fields initialized.
> 2. The implicit name for that argument should clearly be `self`.
> 3. Which combines nicely with object-oriented programming.
>
> Example:
>
> function fct {expr, x=1, y=2, z=3}
> ...
> end
>
> has the same semantics as (_proto being invisible to the user)
>
> function fct(self)
> local _proto = {expr, x=1, y=2, z=3}
> for k,v in pairs(_proto) do self[k] = self[k] or v end
> ...
> end
>
> At a later call, `fct(tbl)` would ensure that tbl[1], tbl.x, tbl.y and
> tbl.z are defined on return. And if `fct` is assigned to `obj.init`, then
> merely `obj:init()` would initialize those fields.
>
At first I thought "of course the self is the answer!", but now I fear
I'm missing something....
local my_class = {}
my_class.properties = {x = 100, y = 200}
function my_class:my_method{expr, x=1, y=2, z=3}
--...
end
How does that code work? I either trampled on x and y or do I have
self.self? Or is it the case that I can't use colon notation and
named/default arguments table?
As much as I must admit that 'self' is far better looking and more
readable, think that '...' might be more pragmatic and actually,
perhaps in the way that less change is better, more correct . To use
your semantic example:
expr = "a - b" --otherwise it's nil and doesn't show a default, which
is fine, of course.
function fct(...)
local _proto = {expr, x=1, y=2, z=3}
for k,v in pairs(_proto) do
(...)[k] = (...)[k] or v
end
return (...)
end
local a = fct{"b + d", y=4,z=5}
print(a[1], a.x, a.y, a.z)
--> b + d 1 4 5
so that your proposed syntax would be equivalent:
function fct{expr, x=1, y=2, z=3}
return (...)
end
However, one other thing needs addressing, as I see it:
function fct{expr, [x]=1, [y] = 2, [z] = 3}
return(...)
end
What is x, y and z? What if they're nothing, right now? What if they
aren't nothing, right now? If the user doesn't assign "x" and "x" is
not assigned when the function definition is created, then does that
throw an error? I don't think that the question is a show-stopper, but
it needs to be dealt with. Since you don't know, and can't know, what
the object keys will be ahead of time, they make assigning default
values to them.... impossible?
I think that the "(...).key" notation in my initial example is so
ugly, that perhaps your definition is best when there are no (). That
is to say, with no () and only a {} after the function name,
"(...)[key] or " is inserted before every assignment. I think this
small addition seems reasonable to me and not too confusing.
On its own, I like this idea and think that it would be a big advantage.
That said, I also like the idea of being able to express the same
default value assignments that are usually done immediately after the
parens. Without your improvement, you would need the "(...)[key]"
method, if no () are allowed:
local function fct{(...).expr or expr, x = type((...).x) == "number"
and (...).x or (...).x == nil and 1 or error("Das swinehund!") ,
y= (...).y or 2, z= (...).z or 3}
return (...)
end
This is clearly too ugly to allow, so either you apply your idea,
another "self"-like keyword (expensive in terms of change) or do away
with the idea that you don't need ().
Example
function fct(arg = {arg[1] or expr, x = type(arg.x) == "number" and
arg.x or arg.x == nil and 1 or error("Das swinehund!"),
y= arg.y or 2, z= arg.z or 3})
return (arg)
end
This has the benefit of being consistent, not only with a named
argument tables, but also with ordered arguments, as well:
function fct(my_value = my_value or 1, a = a or {"my first default
value", some_object})
or:
function fct(my_value = my_value or 1, a = {a[1] or error("can't
forget me!"), a[2] or big_table})
or the crazy:
function fct(a = a or 1, b = b and b + a or 3) end
... given that the above would be equivalent to:
function fct(...)
local a,b = ...
a = a or 1
b = b and b + 1 or 3
end
I'm not sure if this disambiguates the question of object keys:
function fct( args = {args[1] or expr, [args[x] or "x"]=args[x] and
args[x] or 1, [args[y] or "y"] = args[y] and 2 or nil, [args[z] or z]
= args[z] or 3}
return(...)
end
Perhaps the best way to think of this is to ask, "How is this dealt
with now?" It isn't, without a loop, so I guess it's not really an
important question...
So, combining your innovation and default arguments within the
function definition, the language behavior might be stated:
"Inside a function's argument definition, the user's arguments are
processed by order of position, where each argument is assigned the
variable name that is in the same position within the function
definition's argument parens. Extra arguments are assigned to the ...
keyword. The value from the caller's environment is then applied to
these new, function-scoped variables.
If assignments (optional) have been made within the function
definition's argument parens, then they are applied immediately after
the calling environment's values have been applied, again within the
function's scope, just as though the assignments immediately followed
the closing of the argument parens.
When no parens are included and only a single, anonymous table is
used, then "(...)[key] or " is prepended to any assignment, where
"key" is the function definition's index, such that the assignment is
not treated as a static over ride, but instead as a default value, to
be used only when no value was provided by the caller."
I believe that this is the way it works to day (including the
object:method{myvar=default_value} case), with two additional semantic
conveniences, which I believe would add clarity to the code.
Best Regards,
Andrew Starks