Godot-proposals: Ability to pass arguments to functions by name

Created on 26 May 2020  路  7Comments  路  Source: godotengine/godot-proposals

Describe the project you are working on:

Any project.

Describe the problem or limitation you are having in your project:

Each time I add/remove function parameters, change their order, or change which are mandatory, I have to manually hunt for all function calls and update them.
Especially in setup functions that have dependency injection, mode setting etc.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:

The feature allows calling parameters by name.

The future makes changing parameters less of a problem. For instance if I have function calls that use only one parameter of all available ones by name, changing their order or adding new non-mandatory ones does not break the function call.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

The future is already existing in python, and works like this:

func foo(val1:String = "c", val2:String = "c"):
    print(val1)
    print(val2)

foo(val2 = "a") # prints "c a"
foo(val2 = "a", val1 = "b") # prints "b a"

More detailed explanation: https://treyhunner.com/2018/04/keyword-arguments-in-python/

Here is a practical example of usage:

func build_spacefighter(
  hull:int, # Mandatory
  engines:int,
  front_gun:int = 0, # Optional
  side_guns_1:int = 0,
  side_guns_2:int = 0,
  missiles_1:int = 0,
  missiles_2:int = 0,
  torpedo:int = 0,
  targeting_system:int = 0,
  cloak:int = 0,
  shield_generator:int = 0,
  ):

  # Rest of function

var bomber = build_spacefighter(hull = Parts.HULL_A, engines = Parts.ENGINE_C, torpedo = Parts.TORPEDO_PHOTON)

Same code without this function:
var bomber = build_spacefighter(Parts.HULL_A, Parts.ENGINE_C, 0, 0, 0, 0, 0, Parts.TORPEDO_PHOTON)
^ And adding new ship parts before the called ones means changing amount of zeroes in the function call every time.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

Nope.

Is there a reason why this should be core and not an add-on in the asset library?:

It's would be part of GDScript.

core gdscript

Most helpful comment

I haven't seen many criticisms of named parameters in Python or Lua, and for functions taking many parameters, their use increases readability and reduces occurrence of bugs.

All 7 comments

Previous discussion about this proposal can be found here: https://github.com/godotengine/godot/issues/6866#event-3373200829

Just to summarize the technical issues:

  1. Argument names are stripped on release.

    • This is the biggest one that limits this feature. We could add them back on release, but the increased binary size just for this feature doesn't seem worth it.

    • Additionally, if we ever add an intermediate representation to compile GDScript on export, then we could partially worked around, but the next points still apply.

  2. Functions aren't always known at compile time.

    • In fact, unless you use static typing, functions are usually not known at compile time. Like $Sprite2D.rotate(PI), uses get_node which can return any generic Node. If you don't have scene info available (which is always the case when compiling GDScript on its own) you can't tell what the rotate() function parameters are.

  3. Adding this at runtime would incur in massive performance overhead.

    • If we try to resolve this at runtime, at which point we know what the function is, then we would have to spend quite some time figuring out the proper order to pass the arguments. This would hit call performance considerably.

    • Users don't expect this to cause a performance hit, and if they did they just wouldn't use the feature, making it pointless to implement.


Potential solution:

To not be only negative, there's one thing feasible to implement:

If the function is known at compile time then I could use the compiler to de-mangle the arguments and push them in correct order.

With a few natural caveats:

  • If the function isn't known, this would throw a compiler error.
  • Using static types and casting would increase the places where you can use this.
  • The names are obtained from the found function. If you override a function and change parameter names, the named arguments are still dependent on which function is detected (so if it detects the super class, then it uses the names defined there).

I'm not sure if it's worth it adding with these restrictions. If it is, I can consider adding once the main features are working in the new GDScript.

If the function isn't known, this would throw a compiler error.

Is there a deterministic way go know whether the function is or isn't known at compile time? If so, are the "rules" simple enough to understand at a glance? I'm skeptical here.

The names are obtained from the found function. If you override a function and change parameter names, the named arguments are still dependent on which function is detected (so if it detects the super class, then it uses the names defined there).

This would cause so much confusion.

I really don't see how this proposal is really even that useful. In fact, in languages this is supported, its use is often frowned upon because it negatively affects readability and maintainability for the average programmer.

At the very least, I don't think the bebefits here (if any even exist) outweigh the plethora of cons.

I haven't seen many criticisms of named parameters in Python or Lua, and for functions taking many parameters, their use increases readability and reduces occurrence of bugs.

I've never heard of any criticism of named parameters in Kotlin either, and it's generally regarded as an improvement over positional parameters only in Java, where developers have to rely on the IDE to keep track of parameters. Also, no one is suggesting that named parameters replace positional parameters; only that it's offered as an alternative, like in Python, Lua and Kotlin, so there are no cons, unless performance is affected negatively.

This would be nice. Currently I am using an optional Dictionary parameter (performance-wise - that's one allocation):

func format_time_(time: float, options: Dictionary = {}) -> String:

But that has a lot of drawbacks. First, just from a function definition, one cannot tell which options the function supports, so I have to not forget to document them (docs are not great currently):

## Format time.
## @param time {float} Time in seconds
## @param options {Dictionary}
## Options:
## * `format`           - default `TimeFormat.DEFAULT` (hours + minutes + seconds)
## * `delim`            - delimiter, default `":"`
## * `digit_format`     - default `"%02d"`
## * `hours_format`     - default is `digit_format`
## * `minutes_format`   - default is `digit_format`
## * `seconds_format`   - default is `digit_format` (or `"%05.2f"` when TimeFormat.MILISECONDS is in `format`)
...
func format_time_(time: float, options: Dictionary = {}) -> String:
...

And a second drawback, I have to extract them from the dictionary and fill in the default values (performance-wise: for every field in options that's one call to get or GG.get_fld_or_else_). Not only it's not apparent from the function signature what it expects in the options arg, default values are also not present in the signature. So a user can't simply write a name of a function followed by ( in the editor to view the function signature and see what the function supports, he has to navigate to the function definition to read the docs or read the generated docs elsewhere.

2020-06-02_21-04

Reading code is pretty good (compared to named args, it's just few characters longer):

func format_time(time: float) -> String:
    return GG.format_time_(time, {format = GG.TimeFormat.MINSEC, minutes_format = "%d"})

But using such function (writing code calling it) has, in my opinion, pretty bad UX and performance is most surely worse than anything you could come up with in the engine.

Edit: Totally forgot, names of named args could be suggested by the editor (relatively easily I think). Dictionary options would require to have first a way to describe the type (shape) of it (it's not currently possible) and then suggest fields of the options object based on its type (I believe that will be much harder to implement compared to named args suggestions).

This would be nice. Currently I am using an optional Dictionary parameter (performance-wise - that's one allocation)

Another drawback is that these arguments don't get parsed, so you get failures at runtime.

Was this page helpful?
0 / 5 - 0 ratings