Feature requests - syntactic sugar

16 views
Skip to first unread message

Stephan Weinberger

unread,
Apr 22, 2020, 8:56:42 PM4/22/20
to ldmud...@googlegroups.com
Hello,


while cleaning up our lib I noticed a growing desire for some syntactic
sugar :-)

In order of magnitude:


* inherit <xyz>

a hook analogous to H_INCLUDE_DIRS for #include would be nice.


* default values for function arguments

while it is indeed simple to assign default values in a function, having
that in the declaration would sometimes really clean up things

e.g.

foo(int bar = 42) { ... }

could internally just expand to

foo(int bar) { bar ||= 42; ... }

at compile time


* named function arguments

In many libs I find epic contortions for handling varargs, checking
variable types and shifting around values between variables, often not
covering all edge cases (especially for variables of the same type).
Others desperately resort to passing a single mapping, which comes with
it's own caveats (side effects due to call-by-reference; performance
penalty when fixing this by doing a copy). All that could magically
disappear with named function arguments, allowing for cleaner, safer and
faster code.

I dont know if the internal data structure for the function prototype
already includes the variable names (the driver does not complain if a
function declaration uses different names than the implementation), so I
dont know how much additional work this would be, but being able to do
something like:

varargs void foo(int bar, string baz) { ... }

foo(baz : "named arguments rule!");

would be quite awesome.

Note the use of ':', as using '=' may also be a valid assignment to a
local variable of the same name. ':' should not break any code (the ':'
in inline-ifs already works in mappings and foreach(), so it would work
just the same here).

For simplicity the rule for function calls would be either all
positional or all named, varargs arguments would have to be provided as
a single array - exactly as declared - when using named arguments.


cya

  Invisible @ Beutelland



Gnomi

unread,
Apr 23, 2020, 12:47:05 PM4/23/20
to ldmud...@googlegroups.com
Hi,

Stephan Weinberger wrote:
> * inherit <xyz>

This is not that easy. It means the inherit statement then has to be parsed
by the preprocessor instead of the compiler. Because < and > are normal LPC
operators, they cannot be used to introduce a filename (as quotation marks
introduce a string).

I don't know, yet, what consequences this will have.

> * default values for function arguments

For the record: https://mantis.ldmud.eu/mantis/view.php?id=263

This also faces some interesting challenges. The implementation should
differ: If you pass 0 explicitely, the default value shouldn't jump in.
And you probably didn't want to stop at constants for default values,
I guess there will be demand for expressions involving also the other
parameters...

> * named function arguments

For the record: https://mantis.ldmud.eu/mantis/view.php?id=273

This is really complex. There are questions to be answered on 3 sides:

Caller:
* How to specify named arguments.
* How to mix positional and named arguments?
(For example you specify the last varargs argument as named argument
but pass more arguments, that should go into that array, too, if it
is an array.)

Forwarder (like call_other or funcall):
* How to receive all named arguments and forward them to another function.
* How should named arguments that do not denote a specified function
parameter be handled? (Superfluous positional arguments are discarded...)

Callee:
* How to specify optional and non-optional named arguments?
* Are all arguments passable as named arguments or only certain ones?
* How to handle redefinitions (prototype vs. definition or inherited
functions) that change the argument's name?
* Are there wildcard named arguments? (An argument that takes all named
arguments that didn't match a specified parameter, similar to the varargs
parameter for positional arguments.)

Regards,
Gnomi.

Zesstra

unread,
Apr 23, 2020, 1:28:22 PM4/23/20
to ldmud...@googlegroups.com
On 23.04.20 18:47, Gnomi wrote:
> Stephan Weinberger wrote:
>> * inherit <xyz>
>
> This is not that easy. It means the inherit statement then has to be parsed
> by the preprocessor instead of the compiler. Because < and > are normal LPC
> operators, they cannot be used to introduce a filename (as quotation marks
> introduce a string).An easier way for muds may be to use the inherit_file master apply.

Bye,
Zesstra
--
MorgenGrauen -
1 Welt, mehr als 200 Programmierer , mehr als 16000 Raeume,
viel mehr als 7000 unterschiedliche Figuren, 90 Quests, 13 Gilden,
ueber 5000 Waffen und Ruestungen, keine Umlaute und ein Haufen Verrueckter.
Existenz: mehr als 25 Jahre
http://mg.mud.de/

Stephan Weinberger

unread,
Apr 23, 2020, 8:06:43 PM4/23/20
to ldmud...@googlegroups.com
On 23.04.20 18:47, Gnomi wrote:
> Hi,
>
> Stephan Weinberger wrote:
>> * inherit <xyz>
> This is not that easy. It means the inherit statement then has to be parsed
> by the preprocessor instead of the compiler. Because < and > are normal LPC
> operators, they cannot be used to introduce a filename (as quotation marks
> introduce a string).
>
> I don't know, yet, what consequences this will have.


True, would need to be done in the preprocessor, which is somewhat
ugly... I think I'll go with Zesstra's proposal and use the
"inherit_file" apply; seems much cleaner and opens up many more options.

btw. now I'm wondering about the "include_file" apply, which is
obviously called before the preprocessor handles <> vs. "".


>> * default values for function arguments
> For the record: https://mantis.ldmud.eu/mantis/view.php?id=263
>
> This also faces some interesting challenges. The implementation should
> differ: If you pass 0 explicitely, the default value shouldn't jump in.

True, didn't think about that. Too bad we don't have an undef/null -
would often make life easier...


> And you probably didn't want to stop at constants for default values,
> I guess there will be demand for expressions involving also the other
> parameters...

Limiting it to constants would be ok IMHO.


>> * named function arguments
> For the record: https://mantis.ldmud.eu/mantis/view.php?id=273
>
> This is really complex. There are questions to be answered on 3 sides:
>
> Caller:
> * How to specify named arguments.

As for the syntax, see my original post. The caller would need to know
the names of course, as in any language that supports this feature.


> * How to mix positional and named arguments?

For simplicity I'd define it as 'all-or-nothing' rule: either
positional, or named.

The only useful exception might be that you can start with positional
arguments, but as soon as there is a named argument all following args
have to be named as well. This would fit the most common use case:
optional arguments. Duplicates (regardless of positional + named or
using the same name twice) throw an error.


> (For example you specify the last varargs argument as named argument
> but pass more arguments, that should go into that array, too, if it
> is an array.)

See my original post: I'd use the argument types strictly, i.e. a named
call would have to provide an array for a varargs argument. Also see the
example below.


> Forwarder (like call_other or funcall):
> * How to receive all named arguments and forward them to another function.

As a first step, apply/funcall could probably just support positional
arguments (especially because this opens up the whole new topic, that
closures would need to know argument names too). I'd start with
call_other/->.


> * How should named arguments that do not denote a specified function
> parameter be handled? (Superfluous positional arguments are discarded...)

Personally I'd like superfluous arguments to throw an error anyways, as
ignoring this silently often leads to strange bugs that are hard to
find. But I'd also be fine with the same behavior (silently ignore).


> Callee:
> * How to specify optional and non-optional named arguments?

Currently a varargs function will take any number of arguments and we
cannot specify which ones should be optional and which ones are required
- it's always "all or nothing"...

But actually a more refined 'optional' modifier would be a nice feature!

E.g.

void set_name(string name, optional string article = "a", optional
varargs string * adjectives) { ... }

-> set_name("box", "the", "small", "green");

-> set_name(name: "box", adjectives: ({"small", "blue"}));

-> set_name(article: "a", adjectives: ({"large"}), name: "box");

and - if positionals in the beginning are allowed - just:

-> set_name("box", adjectives: ({"small"}));

-> set_name(article: "the") => error: missing argument 'name'

-> set_name(article: "a", "box") => error: unnamed arguments are invalid
after named arguments


as opposed to what we have now:

varargs void set_name(string name, string article, varargs string *
adjectives) { if (!name) raise_error(...); article ||= "a"; ... }

-> set_name("box", 0, "blue", "long");


This would of course mean that an 'optional' argument would require all
following arguments to be 'optional' as well (so that positional calls
still work the same). The 'varargs' function modifier would then have
the same effect as setting all arguments 'optional'. Also, a default
value would obviously only make sense for 'optional' arguments.


> * Are all arguments passable as named arguments or only certain ones?

I don't see any benefit in having a "he who shall not be named" argument.


> * How to handle redefinitions (prototype vs. definition or inherited
> functions) that change the argument's name?

At the moment the driver doesn't care about mismatched names and just
uses the ones specified in the implementation. This is kinda strange
anyways and I'd rather have a compiler error. Same for inherited
functions; renaming arguments in an inherited object just leads to
confusion... (at least there is #pragma warn_function_inconsistent but
AFAIK this only checks the number and type of arguments).

From my experience the temptation to do this often arises exactly
*because* handling optional positional arguments is such a mess when you
don't have polymorphism (which would be a nice alternative btw.; but it
would of course be a different nightmare because of
apply/funcall/call_other which don't check the types).


> * Are there wildcard named arguments? (An argument that takes all named
> arguments that didn't match a specified parameter, similar to the varargs
> parameter for positional arguments.)
I wouldn't bother with his. The whole point of named arguments is to
make the call more explicit, so the caller has to know what arguments to
provide anyways. Hence any "automagical" handling of unknown arguments
would be counter-productive; therefore an unknown argument should just
throw an error.


Regards,

  Invisible



Zesstra

unread,
Apr 24, 2020, 3:19:58 AM4/24/20
to ldmud...@googlegroups.com
Hello Stephan,

just a few comments from me as LDMUd user. ;-)

On 24.04.20 02:06, Stephan Weinberger wrote:
> True, didn't think about that. Too bad we don't have an undef/null - would
> often make life easier...

But introducing it now will pose a challenge for (large) mudlibs.

> Personally I'd like superfluous arguments to throw an error anyways, as
> ignoring this silently often leads to strange bugs that are hard to find.
> But I'd also be fine with the same behavior (silently ignore).

I know places in our mudlib, where this is deliberately used and I expect a
significant amount of breakage when we make this an error.

>> * How to handle redefinitions (prototype vs. definition or inherited
>> functions) that change the argument's name?
>
> At the moment the driver doesn't care about mismatched names and just uses
> the ones specified in the implementation. This is kinda strange anyways and
> I'd rather have a compiler error. Same for inherited functions; renaming
> arguments in an inherited object just leads to confusion...

Uh. The MG lib is riddled with inconsistent names for arguments all over the
place (just because it is OK to do it, some people would call it a feature).
It will blow up hundreds if not thousands of objects if we require name
consistency for arguments across the whole inheritance hierarchy.

Adding this requirement will cause endless pain in larger mudlibs. We could
add an optional warning for this, but I will strongly advise not to make it a
non-optional error.

Requiring argument names to be consistent for prototypes and definition is a
less critical change, although it will also cause a lot of breakage.

> don't have polymorphism (which would be a nice alternative btw.; but it
> would of course be a different nightmare because of
> apply/funcall/call_other which don't check the types).

Yes, polymorphism would be nice in principle, but the nightmare is in the
existing code base where every inconsistent function definition will create a
new one. Adding it will again require to check and rewrite *a lot* of code.
Reply all
Reply to author
Forward
0 new messages