Right now, the function a funcref (or a object-methodname pair) points to needs to be declared separately, which is inconvenient for small one-liners, especially in cases like connect()
.
This is a proposal for some syntax that would automate the creation of these one-off functions. Keep in mind that this does not entail having functions as first-class citizens (yet), just a compile-time nicety. The generated function would still exist and have a name in the bytecode.
This would also have the side-effect of allowing things like connect()
and Tween.interpolate*()
to receive funcrefs directly, in addition to object-methodname pairs.
My proposal is inspired by JavaScript's notation, rather than Python's (since :
now has typing connotation):
() -> return_value
param -> return_expression * 2
(param1: String, param2: int) -> (x = return_statement)
Just like Python, multi-statement lambdas would not be allowed.
The variable context would be that of the closest class, so
var x: int = 20
class Baz:
var x : int = 10
var y : FuncRef
func foo():
y = ()->print(self.x) # lambda expression starts in line 8, column 12
func bar():
y.call_func() # prints 10
would compile to the same bytecode as something like
var x : int = 20
class Baz:
var x : int = 10
var y : FuncRef
func foo():
y = funcref(self,"__anonymous_function_8_12")
func bar():
y.call_func() # prints 10
func __anonymous_function_8_12():
print(self.x)
Some usage examples could be:
connect("signal_name", x -> baz(x*10) )
$Tween.interpolate_method( (x)->(y = x * x) ,0.0,1.0, ...<remainder of Tween's args>)
And it would fit nicely with map, reduce and filter, which have also been requested.
[1,2,3].map(x -> x*2) // returns [2,4,6]
[1,2,3].filter(x -> x%2==1) // returns [1,3]
[1,2,3].reduce((x,y) -> x + y) // returns 6
[1,2,3].reduce((x,y) -> x + y, 7) // returns 13
As a bonus, we could detect if any properties from self
are being used and otherwise define the generated function as static
(which also would require funcref
support for static functions).
My idea for this would be to allow multiline lambdas as well. Somewhat like this:
func _ready():
print("okay")
var lambda = ():
print("inside")
print("also inside")
lambda()
connect("toggled", x: print(x))
[1,2,3].reduce((x, y): x + y, 7)
[1,2,3].map((x: int):
var temp = x * 2
return pow(temp, temp)
)
var transform = x:
return x * 2
# Binding from external context (syntax can change)
[1,2,3].map((x).(transform): return transform(x))
Might be a little trick to implement all the nuances with indentation, but should be doable.
@jabcross suggested replacing the :
with ->
for lambdas. I'm considering though I think it's a bit inconsistent, since indented blocks usually start after :
.
My idea for this would be to allow multiline lambdas as well. Somewhat like this:
func _ready(): print("okay") var lambda = (): print("inside") print("also inside") lambda() connect("toggled", x: print(x)) [1,2,3].reduce((x, y): x + y, 7) [1,2,3].map((x: int): var temp = x * 2 return pow(temp, temp) ) var transform = x: return x * 2
I really like this idea, and I would make the parenthesis mandatory, like this:
func _ready():
print("okay")
var lambda = ():
print("inside")
print("also inside")
lambda()
connect("toggled", (x): print(x))
[1,2,3].reduce((x, y): x + y, 7)
[1,2,3].map((x: int):
var temp = x * 2
return pow(temp, temp)
)
var transform = (x):
return x * 2
That way, every time one sees (...):
means lambda without doubt.
I find this part a bit confusing:
[1,2,3].map((x).(transform): return transform(x))
Why would I want to do this:
func _ready():
print("okay")
var lambda = ():
print("inside")
print("also inside")
lambda()
Instead of this:
func lambda():
print("inside")
print("also inside")
func _ready():
print("okay")
lambda()
Isn't the latter more readable as it requires knowing fewer syntactic rules?
@aaronfranke In the example you mention, a lambda wouldn't be worth it. But in other cases such as:
map
, reduce
and filter
*...lambdas are worth it. Specially when a function doesn't need a function name.
(*) I am currently using these kind of functions in a Godot 3.1 project with funcrefs
, and looks very hacky.
I have recently published an experimental library Golden Gadget where lambdas are simulated (not ideal - essentially eval under the hood) and for some functions they are directly usable:
# monsters is an array of objects
var res = G(monsters).filter("x => x.is_alive && x.hp < 10").map("x => x.name").val
It also has a shorthand for creating funcref
s of anonymous functions (without closures):
F("x => x.b").call_func({b = true}) # true
At least until lambdas are in GDScript this might be a good enough solution. Of course all depends on your use case - array wrapper isn't free and while lambdas are being cached, their performance will undoubtedly be worse than native func
.
https://github.com/godotengine/godot/issues/17268#issuecomment-548822364
Looks like we're gettting first class functions with Godot 4.0 :)
https://github.com/godotengine/godot/pull/39093
Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.
The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.
If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!
Most helpful comment
@aaronfranke In the example you mention, a lambda wouldn't be worth it. But in other cases such as:
map
,reduce
andfilter
*...lambdas are worth it. Specially when a function doesn't need a function name.
(*) I am currently using these kind of functions in a Godot 3.1 project with
funcrefs
, and looks very hacky.