system
November 17, 2021, 4:14am
1
Attention
Topic was automatically imported from the old Question2Answer platform.
Asked By
V1Ultrakill
I’m pretty new to the GDScript language, but I know that functions in GDScript aren’t first class, so you can’t store them in variables. I know about FuncRef and how to use them to (somewhat) store functions in variables, but I don’t think it works with built-in functions, like floor and round.
I’m (more, at least) familiar with Python, and in Python i can store functions and call them in this way:
functions = [abs,max]
foo = functions[0](-1) # basically abs(-1), which is in turn 1
I’m trying to do this in GDScript, what’s the closest GDScript equivalent?
I’ve tried to RTFM, but I don’t think i’ve seen any mentions of FuncRefs being used with built-in functions in the docs.
There are some workarounds I can do i.e. wrapping the built-in function, but it feels dirty.
system
November 17, 2021, 6:19am
2
Reply From:
dewcked
I think there already is implementation of lambda function in godot 4.0 (but not backported)
see below
opened 06:29PM - 08 Mar 21 UTC
closed 02:45PM - 28 Apr 21 UTC
topic:gdscript
### Describe the project you are working on
GDScript.
### Describe the pro… blem 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:
```gdscript
func _ready():
var my_lambda = func(x):
print(x)
```
They can also be written in a single line, like regular functions:
```gdscript
func _ready():
var my_lambda = func(x): print(x)
```
You can add a name to it, for debugging purposes (useful when looking at call stacks):
```gdscript
func _ready():
var my_lambda = func my_lambda(x): print(x)
```
You can pass an inline lambda as a function argument:
```gdscript
func _ready():
button_down.connect(func():
print("button was pressed")
)
```
Lambdas are treated like `Callable`s so to call those you need to explicitly use its `call()` method:
```gdscript
func _ready():
var my_lambda = func(x): print(x)
my_lambda.call("hello")
```
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.
```gdscript
func _ready():
var x = 42
var my_lambda = func(): print(x)
my_lambda.call() # Prints "42"
x = "Hello!"
my_lambda.call() # Prints "42"
```
This still allows you to create lambdas that act differently given the context, like this:
```gdscript
func make_lambda(x):
var my_lambda = func(): 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!"
```
#### 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.
godotengine:master
← vnen:gdscript-lambda
opened 11:21AM - 29 Mar 21 UTC
Closes godotengine/godot-proposals#2431
See the referenced proposal for the s… yntax details.
Lambda functions are compiled as GDScriptFunctions and stored on their parent function, allowing lambdas to be nested too. At rumtime, they create a GDScriptLambdaCallable, which is used to reference the function itself and the potential captures. The captures are applied as extra arguments in the beginning of the list, but this is transparent to the users. This does incur in an extra performance penalty for lambdas with captures, but it shouldn't be too much.
I think this might be possible with lamda?