godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Add optional syntactical sugar for one-liner lambda expressions #9736

Open FabriceCastel opened 6 months ago

FabriceCastel commented 6 months ago

Describe the project you are working on

A 2.5d resource management game

Describe the problem or limitation you are having in your project

One-liner lambda expressions are needlessly verbose/visually busy. Coming to gdscript from any language with nicer lambda syntax (js, scala, C#, heck even java..) makes using one-liner lambdas painful and harder to parse at a glance.

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

Add support for optional one-liner lambda syntactic sugar to support a lambda syntactical shorthand shared by many other programming languages:

my_class.some_method((a: int, b: float) -> a + b) # typed
my_class.some_method((x, y) -> x + y) # untyped

Current gdscript lambda syntax would require these to be:

my_class.some_method(func(a: int, b: float): return a + b) # typed
my_class.some_method(func(x, y): return x + y) # untyped

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Terse lambda syntax:

For each of the examples below I'll list two lines of code; my proposed syntax, followed by the current gdscript equivalent. These side by side comparisons make the case for this proposed syntax's improved legibility/visual noise reduction.

var array = [1, 2, 3, 4, 5]

# Untyped, param brackets can optionally be omitted for single-param lambdas
array.map(x -> x + 1)
array.map(func(x): return x + 1)

# Type declaration for input param
array.map((x: int) -> x + 1)
array.map(func(x: int): return x + 1)

# Multiple input params, untyped
var my_lambda = (x, y) -> x + y + 10
var my_lambda = func(x, y):
  return x + y + 10

# Param-less lambda
var no_params = () -> 100
var no_params = func():
  return 100

The visual noise becomes worse when expressing a chain of operations, eg. (and yes, obviously this is a contrived example)...

array.map(x -> x + 10) \
  .filter(x -> x > 0) \
  .map(x -> str(x) + " dollars")

# versus...

array.map(func(x): return x + 10) \
  .filter(func(x): return x > 0) \
  .map(func(x): return str(x) + " dollars")

I know something similar has been proposed about a year ago in https://github.com/godotengine/godot-proposals/issues/6614 and also rejected in the original lambda proposal. This proposal differs in that it is more narrowly scoped to exclusively handle one-liner lambda expressions. To define multi-line lambdas, you would have to use the regular func()... syntax.

Limiting the implicit return functionality to this particular syntactic sugar also addresses another complaint I've seen brought up when discussing the possibility of making return implicit for all func/lambda, in that I'm not suggesting any changes to existing behaviour.

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

Not really, no - though if this proposal gets shot down I may very well resort to some hack-job code preprocessor script that will allow me to use this syntax and "translate" it to the func() syntax under the hood. I'm not excited by the prospect of the work/workaround required though.

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

It's gdscript syntax

dalexeev commented 6 months ago

vnen:

I personally don't want to add arrow functions because it would be difficult to parse (the => token appears way too late to easily disambiguate).

We intentionally added this limitation. This simplifies implementation and forces us to keep GDScript syntax easy to read.

This

my_class.some_method(func(a: int, b: float): return a + b) # typed
my_class.some_method(func(x, y): return x + y) # untyped

can probably be shortened to

my_class.some_method(func(a: int, b: float) := a + b) # typed
my_class.some_method(func(x, y) = x + y) # untyped

But is it worth it?

FabriceCastel commented 6 months ago

Is there no mechanism to essentially write either custom (or in core) gdscript pre-processors that could expand this and possibly other bits of syntactic sugar into the full func() expression before it gets interpreted? Seems like that would open the door to a lot of nicer shorthand notation without requiring work in the core gdscript parser - though I'd imagine that probably wouldn't be trivial to add, and might be denied for performance reasons...

As for this, I can understand pushback on the basis of parsing difficulty, but it seems.. pretty unsatisfactory. I guess I'll have to take that up with vnen.

The shorthand you've got here,

my_class.some_method(func(a: int, b: float) := a + b) # typed
my_class.some_method(func(x, y) = x + y) # untyped

Is.. well, I guess it's strictly better than what we've currently got? But it's doing some even more oddball/non-standard stuff with the way to express lambdas vs... most other languages. It feels much more like a band-aid than a genuine solution to the problem, which is fundamentally that current godot lambda syntax is not good for multiple short inline uses :/