This is not so much a help question as a style question. The fact that GDScript does not allow nested functions seems absolutely mental to me. You can get by with named lambdas, but as far as I can tell, you then always have to use my_lambda.call() instead of my_lambda, and you get the insane behaviour of being able to modify variables in your outer function if they are Objects or non-scalars, but not if they are built-in scalar types.
This leads to me writing a lot of code of the following type:
var stuff = { counter : 0 } # NOT: var stuff: int = 0 :(
var increment = func increment(): stuff[counter] += 1
# do some cool stuff
increment.call()
If increment modifies a lot of values, it is not feasible to solve this by using its return value, since I would have to return a dictionary and then assign all the values to the correct variables, making even more overhead since increment is called in several places.
As far as I can see, the only options for the (presumably very common) use case of “reusing some code within a function that is not side effect free” are:
What I showed above.
Make an inner class, because lambdas are allowed to modify objects.
Make all variables global, because lambdas are allowed to modify global variables.
Copy and paste the code you want to reuse.
Since 3. and 4. are clearly insane, that only leaves “have a random dictionary floating around”, which loses type safety, and “use an inner class”, which seems an awful lot of overhead since that class will be used in literally one function.
Am I missing a secret fifth option? If not, which of 1. or 2. do you prefer and why?
Exactly what I wrote in my post: Sometimes you have a code snippet that you only need to reuse inside one function. Sure, you can make everything global and use class methods, but then you have a bunch of variables in the global scope of your class that are supposed to be local, just because you wanted to reuse some code. Even worse, because GDScript has no strict scoping, this exposes those variables to the outside world.
If you have a better understand of how Lambdas really work under the hood and given GDScript copies basic scalar types (and strings kind-of) and references complex types (anything with .duplicate) I believe it would make more sense to you.
Lambdas are fundamentally a function call like any other, the big difference is instead of automatically including self as an argument, every currently scoped variable is included as an argument. In C++ lambda functions give programmers a lot of choice in what is “captured” and one must explicitly capture by reference/pointers or by copy. Adding such functionality to GDScript means implementing references/pointers for scalar types, driving up the language complexity and stretching the syntax even further.
int a = 123;
float b = 456;
// capture reference to a, copy of b, argument c
const auto my_lambda = [&a, b](std::string_view c) {
a += 1;
std::cout << c << ": " << b << std::endl;
}
my_lambda("b_value");
Personally I’ve had a falling out with C++, (now zig is my best friend) I used to love lambdas but knowing how they work under the hood, and how dangerous/high-overhead [=] captures are (all by copy), I avoid the funny syntax as much as possible. And I’ve been quite successful, I don’t reach first for lambdas in Godot and I haven’t had trouble keeping my code pleasant to work with. I think the secret fifth option is to make a normal function and be aware of what will be copied and what data types you want to use if mutating a value.
If you have any specific code I’d be happy to help review it, but talking theoretically it’s hard to choose from options 1 through 4, other than don’t do #4.
Thank you for your thoughts. I know why lambdas in GDScript work like they do, I just do not like it. I wish I could just pass a reference to my integer variable and be done with it, but I am not even allowed to do that. Every other language I have ever worked with had pointers, I do not understand why GDScript chose to not even give the programmer the option.
You say to just use a normal function, but does that not also involve passing a dictionary with all my variables into it?
I think the actual answer to my problem may be “do it in C++”, since I am implementing some very low-level stuff. It just now occurs to me that GDScript may simply not be made for that use case. Classic case of blaming the tool for doing exactly what it was designed for?
I hope you are aware that it is possible to write bad code also with C++. If you want a programming language that prevents you to making any kind of bug in any situation, you are out of luck.
It puts you back in the exact situation from my original post. You don’t think wrapping a scalar variable inside a dictionary just to be able to pass it into a function is a very odd construction?
Pointers open up a whole class of errors, references provide close functionality with less of the headache and support; the making of GDScript isn’t just about the language but also the tooling around it and fitting into a game engine ecosystem. Maybe there’s a world where GDScript has pointers, but I think it was simpler to get started without it and it’s here to stay. Other languages have proven staying power without pointers such as Python, Java, and C# so while I agree I love the power given with pointers, there’s something to the current system.
From your other posts it sounds like an inner class may be a better fit than dictionaries; do you need the dynamic keys provided by dictionaries?
Could be the case, I’ve made a GDExtension for emulating 32bit RISC-V, a task that would be made much more difficult by GDScripts lack of low-bit types and no pointers. What are you working on?
You are right, an inner class is probably the way to go. It just “feels” like overhead, but it is really just a struct, correct?
I am writing a parser for a custom scripting language, so in reality there is no need to do this with GDScript. It was just, you know, already open. I have written parsers and compilers in C and just started doing the same thing, which was probably a bad call.
It’s very likely less overhead than a Dictionary, there is a sizeof function if you’d like to check in-depth.
May very well be easier in a GDExtension, I’ve written dialogue DSLs in GDScript running bytecode isn’t too difficult. You’ll still have access to Godot’s string library which is copy-on-write so very efficient parses can be written in either GDScript or extensions without copying strings on accident. Maybe split the workload too, maybe parsing is easier in GDExtension and running in GDScript. Point being GDScript is still capable of work on domain specific languages, it may yet depend on what your language’s goals are, but I’m sure you can figure out where to draw that line.
This example is not helpful at all, because it’s not real.
Based on reading the whole thread, I think your problem is that you do not understand how Godot works and you’re trying to shoehorn in old patterns instead of learning Godot’s patterns.
The only time I use lambdas in GDScript is when I have one-line calls.
This is a stupid example, but hopefully you get what I mean.
As @gertkeno said, without specifics, all we can do is talk philosophy and religion. And there are plenty of opinions about how to code on here.. Especially from us professionals.
I think you would benefit from taking a step back and giving us a concrete example of what you are actually trying to do. Because either we can give you the Godot way, or we can tell you that your best bet is GDExtension.
My best guess is you should probably be using Resources to represent these Dictionaries, and letting them do this increment() call.