-
-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add lambda functions in GDScript #2431
Comments
Will regular |
Clean, simple and has a lot of functionality, love it. Would this improve gdscript's refactorability, allowing to easily rename a function everywhere? Currently it has to be a string replacement since functions are stored as strings sometimes, like when connecting signals... |
I was rather perplexed by lambdas in the past, and by considering that I came to understand what they are and why they're useful just by reading your proposal, I'm giving it massive thumbs up! |
awesome improvement, I would like to propose changes in syntax to be something like javascript/C#: func _ready():
var my_lambda = (x) =>
print(x) func _ready():
var my_lambda = (x) => print(x) func _ready():
var my_lambda = my_lambda(x) => print(x) func _ready():
button_down.connect(()=>
print("button was pressed")
) func _ready():
var my_lambda = (x)=> print(x)
my_lambda.call("hello") func _ready():
var x = 42
var my_lambda = ()=> print(x)
my_lambda.call() # Prints "42"
x = "Hello!"
my_lambda.call() # Prints "Hello!" func make_lambda(x):
var my_lambda = ()=> print(x)
return my_lambda func _ready():
var lambda1 = make_lambda(42)
var lambda2 = make_lambda("Hello!")
lambda1.call() # Prints "42"
lambda2.call() # Prints "Hello!" Also I think that lambdas can detect identation like funcs:
this also complements what @menip said. |
Regular functions can already be passed around as Callables. So there is a consistency between both. |
@nonunknown also to use |
@nonunknown this just makes the parsing harder and it's inconsistent with the rest of the GDScript style. C# and JS are not indentation based, so they follow a different style. |
@vnen I was thinking about this now LOL, but at least consider python then? |
@nonunknown the main difference is not adding parentheses, which could be okay but it's just easier to have those. Lambdas in Phyton are also single expressions, while in GDScript I want them to be full-fledged functions. So using the word |
hmm, makes sense! |
Lambdas as full fledged functions? Sounds good! A static lambda that doesn't have a capture list... can't wait. Or maybe lambdas could have a capture list like in certain popular demonized language (no, not Java) so we could specify which variables get captured (or just capture all)? |
There's no tool for refactor yet, but you can already (in Godot master) use function names directly instead of strings, which will help when such tool is made. |
Awesome! Then I think using |
How do you pass such a lambda to a function taking other parameters after it? I don't see clearly how comas and indentation would mix here |
BenjaminNavarro: do you mean like this? func call_lambda(l, arg):
l.call(arg)
func _ready():
var my_lambda = func(x): print(x)
call_lambda(my_lambda, 2) # prints "2" Edit: the idea is that for a more complex lambda, you can define separately and pass it in. However, it is still lambda, not function, and is not available globally. |
It will depend on your style. Functions with many arguments can get weird anyway. If you really prefer doing like this instead of declaring a variable like @menip sugested. func _ready():
#1
call_lambda(func(x):
print(x)
print("Not that bad I think")
, "Other Argument"
, "Yet another Argument")
#2
call_lambda(
func(x):
print(x)
print("Not that bad I think")
, "Other Argument"
, "Yet another Argument"
) |
Yeah, the example by @luizcarlos1405 would be the way. You don't need the blank line if you don't want to. |
@vnen why require the call method in lambda? Seems it would be even cleaner to allow:
|
@menip @luizcarlos1405 @vnen func _ready():
var my_lambda = func(x):
print(x)
print("Not that bad I think")
# usual multiline function call style
call_named_lambda(
my_lambda,
"Other Argument",
"Yet another Argument"
)
# specific function call style
call_inline_lambda(
func(x):
print(x)
print("Not that bad I think")
, "Other Argument"
, "Yet another Argument"
) I guess you could also do this: call_inline_lambda(
func(x):
print(x)
print("Not that bad I think")
,
"Other Argument",
"Yet another Argument"
) But it's also a bit odd. The GDScript style guide makes use of trailing comas so I guess with this lambda syntax we would need to add a dedicated |
Now that lambdas will solidify higher-order functions in GDScript, how would type annotations work for them? Will you be able to type-annotate a parameter/variable that takes a function? Like |
@katoneko Something tells me that it would just be
And if the function takes several arguments, it will be something like this: f("arg1", (func(): print "Hello!"), "arg3") ? |
@BenjaminNavarro so, in my view you should also be able to use trailing commas: func _ready():
call_inline_lambda(
func(x):
print(x)
print("Not that bad I think"),
"Other Argument",
"Yet another Argument",
) But they might get a bit hard to see in this style |
@katoneko that was something I thought about, but I don't think there's an easy way to enforce that the signature matches in every situation. So, at least for now, you only have the |
@ee0pdt it's the same issue with Callables in general: given GDScript is a dynamically-typed language by default, to make this work I would have to assume that every call is to a Callable, but in order to do that I would have to actually make a Callable on every call, which in turn hurts performance. It was my preference as well, but I could not figure out a way to make it happen without putting a performance penalty on every call. |
@noidexe yes, the argument will shadow the variable, but I think that's fine. We could also throw a warning if that is an issue. |
I've change the proposal because passing captures as reference will be too complex and the use cases for that are not common. Captures will be passed as copy instead. |
Can't this be solved by introducing a lambda keyword?(I know about your reply to not introducing lambda keyword in twitter.) Like this:
instead of
The point of introducing the lambda keyword is to make sure that you know that this call is to a Callable and it can directly call .call() under the hood.I don't know how callables work.Does it still affect performance this way? Additional reason would be readability. Anyone would instantly know that it is a lambda function and not a normal function that got misplaced. |
@AnilBK the keyword makes no difference. In the same script you can tell it's a lambda, but once you start passing this around it's not possible anymore given the dynamic nature of GDScript. For example, imagine two files like this: # a.gd
class_name A
var x
func _init():
x = func(a): print(a)
func y(a):
print(a)
# b.gd
class_name B
func _ready():
var a = A.new()
a.x("Hello")
a.y("Hello") The call to Regular functions are also Callables BTW, so this would also work fine: func _ready():
var a = A.new()
a.x.call("Hello")
a.y.call("Hello") Except that the So the problem with this |
Heh, this got me thinking and I might be able to allow the direct |
Actually, nevermind. It would be too hacky 😢 |
Oh. |
Is there a reason why they will be With this approach one could maybe also allow
Rule 1. works as lambdas can only be reached by name in the scope where they are defined, hence it is possible to infer that they are callable. To make them available anywhere else, one would explicitly assign them to a variable or pass them around as a method arg. Just like regular functions. |
I prefer to be explicit in this case. Those are not supposed to be nested functions, even if they can act like so. If you define a lambda function you must assign it to something in order to access it later.
Yeah, but this complicates the compiler for only a marginal syntax gain. I really prefer to have it explicit. If I could do it everywhere then it would make sense, but confining to a particular situation doesn't seem very helpful. |
This comment has been minimized.
This comment has been minimized.
hmm looks for me 'lambda' is more a FuncRef and not a real lambda
i can achive the same by
The real power of lambda is use in functional style like this
EDIT: godotengine/godot#38645 gives me the answer ;) |
@MikeSchulze Did you see godotengine/godot#38645? |
@Calinou uh i had now a look ;)
This saves by day |
Lambdas sound pretty great! I know the documentation remains unwritten; but am I correct in understanding your note, @vnen, that these lambdas capture the scope via copy vs. by reference? i.e., the following would print
As a side note, if this copying is true, I assume this can still be accomplished with a reference-based value like a dictionary or an array, correct?
Lastly, are nested lambdas supported? Although uncommon, this would be helpful in very complicated algorithms.
|
This comment was marked as off-topic.
This comment was marked as off-topic.
Some examples and wordings were copied from the original proposal godotengine/godot-proposals#2431. This also contains notion of one line functions.
Some examples and wordings were copied from the original proposal godotengine/godot-proposals#2431. This also contains notion of one line functions.
Some examples and wordings were copied from the original proposal godotengine/godot-proposals#2431. This also contains notion of one line functions.
Describe the project you are working on
GDScript.
Describe the problem or limitation you are having in your project
There are many times when you need to pass a function around, like when connecting signals, using the Tween node, or even sorting an array. This requires you to declare a function in another place in the file, which has a few issues.
If you're not using the function anywhere else, it's still available publicly to be called, which might not be wanted. It also has to live decoupled from where it's defined, which is not great for reading code, as it requires a context switch. This is particularly problematic if you want to rely on a local variable in scope when you connect the signal (you are forced to add it as a bind).
Describe the feature / enhancement and how it helps to overcome the problem or limitation
With lambdas, you are able to create functions in place. This allows you to make temporary functions without having to define it in the class scope somewhere else, solving the issues above. So you can write the signal callback at the same place you connect it. No need to switch context nor make a publicly available function.
This also allows for more functional constructs, like mapping a function over an array instead of a
for
loop, or passing a function to sort an array.Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Syntax
The syntax for lambdas can be very similar to functions themselves, reusing the
func
keyword and using indentation to delimit the body:They can also be written in a single line, like regular functions:
You can add a name to it, for debugging purposes (useful when looking at call stacks):
You can pass an inline lambda as a function argument:
Lambdas are treated like
Callable
s so to call those you need to explicitly use itscall()
method:They're also closures and capture the local environment. Local variables are passed by copy, so won't be updated in the lambda if changed in the local function.
This still allows you to create lambdas that act differently given the context, like this:
Implementation
Under the hood lambdas are like any ol' GDScript function, compiled to the same bytecode. They will be stored in a different place since they won't be publicly accessible, so you can't call a lambda without passing a reference to it.
Lambdas will use a Callable wrapper when they are created at runtime, pointing to the already compiled function. This will be a custom callable that will also have references to the enclosing environment, allowing closures to work as expected. When a function exits it will copy the closed on variables and store on that Callable, so the lambda can still exists after the function where it was created is out of the stack. This can be done efficiently by copying only values that were actually used in the lambda.
In the end it should have about the same performance of any function call.
If this enhancement will not be used often, can it be worked around with a few lines of script?
This can be worked around somewhat by defining the functions elsewhere and using Callables, but it does fall into the same issues described here. In particular, you cannot make this function private to ensure it won't be called outside its scope. You cannot easily work around closures.
Is there a reason why this should be core and not an add-on in the asset library?
It needs to be part of the language implementation.
The text was updated successfully, but these errors were encountered: