-
-
Notifications
You must be signed in to change notification settings - Fork 98
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 support for immutable/one-time assignable variables #820
Comments
You can make a setter function with setget and only assign a value if it is null. |
@Jummit only if you are talking about a member variable, not if this is a local variable. |
For reference, quoting my own message from godotengine/godot#23695:
Allowing this would likely complexify the implementation a fair bit, so I'm not sure about it. (Just a gut feeling, I'll let vnen decide on this.) |
Using if/else in its expression form and introducing or improving match to be an expression would IMO solve the problem more elegantly (without the need of flow analysis which I imagine would be quite a feat to implement). |
Local variables do trigger setter and getter functions when used with Unrelated, I'd also like to state that |
I use Other suggestions:
or just allow local constants.... But I'm guessing the reason that wasn't allowed is because of philosophy over the definition of what |
Honestly, this is not the purpose of the heading. Making a proposal "I want X" then say that's because "I can't do X" isn't very informative. The question is: why do you need variables to be only assignable once? I know it's an interesting programming concept that some languages implement. But why and how would that be beneficial in GDScript?
Not sure what such philosophy is, but in Godot 4 local constants will be allowed. From the perspective of implementation it's a no-brainer since now it can reuse almost all of the code made for class constants (which wasn't the case before). |
Well, normally, I imagine it would be that a keyword like |
Fair point about the heading. It wasn't meant to be facetious, but I suppose I could have added a bit more to it. Local val x: int
if(condition_1)
x = 1
else if(condition_2)
x = 2
else
x = -1 If someone goes back to maintain this code, it's fairly resistant against mistakes. Removing the The more common use case of just assigning a local that shouldn't be changed is covered by local const though, so that would solve a lot of my own use cases as well. |
I think some mention should be made about the concept being something being visibly immutable. For example, say I have the following value: class_name Player extends KinematicBody
var _breadcrumbs: = PoolVector3Array(); Then I wanted to expose it by providing a get function. func get_breadcrumbs(index: int) -> PoolVector3Array:
return self._breadcrumbs; Visibly const would allow you to guarantee that the callsite cannot modify the data structure while having it mutable as usual within the origin class, by providing a limited view to the data without having to write multiple sets of getters. func get_breadcrumbs(index: int) -> readonly(PoolVector3Array):
return self._breadcrumbs; This is just spit-balling however, as I don't really use GDScript enough yet to know if this would have any implications that would break existing code or go against any sort of design philosophy with it. |
I wanted to make a proposal for this but i'm not sure if it's worth a proposal and it makes more sense in the current context so i'll mention it here. The idea is to add It'd be useful mostly for caching child nodes which now is usually done with
Why does it make more sense in the context of this proposal? Because |
Well, seems that using:
Seems to be good options! Also This could let us export those imutables, for example, lets suppose I want a constant speed variable, but I want to export it so I can change it: @export imut MAX_SPEED := 10.0 # This can be changed only in-editor |
Hi, I need this Feature Here is where (Godot 4.0 beta 7):
You see that So let's just pretend it's 2:00 am in the Morning and I make the mistake of writing If the variable would be immutable, the debugger would point me right to the codeline, where I try to assign a value to an already locked variable But without it I'd be noticing that my children aren't getting updated anymore So please |
4.0 is in feature freeze. This may be added later in 4.x because it doesn't break backwards compatibility. The current workaround is var my_var = init_value:
set(value): assert(false, "my_var is readonly") |
@MaasTL right here you are changing the value, so immutability would not solve for you because the This case could be worked around with a setter: var _is_ready: bool = false:
set(value):
_is_ready = _is_ready or value This way it can only ever change from This case would require you to change at runtime the immutability state of a variable which I think it's a bit dangerous and also harder to enforce (no compile-time checks would be possible for instance). I believe it would be better solved by access modifiers if we want to go that route. |
Regarding keyword, I believe it's much better to find an affix for Or an annotation, which is much more compatibility friendly, though I do prefer avoiding annotations for this kind of thing. |
For most cases |
Any keyword that makes the variable immutable is fine. It can be # Defined a read-only var variable, it can be any keyword
class_name ParentClass
readonly var MYCLASS := CustomClass.new() #1st assignment
func _init()
MYCLASS = CustomClass.new() #2nd Assignment
Which assignment is final? |
"let" will probably confuse people coming from webdev. In modern JavaScript, let is a way to define scoped reassignable variables I know JS is the only language using let this way... But it's also the most widely used language worldwide ( https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/ ) and GDscript also contains a "var" keyword just like JavaScript, so the confusion potential for new users is quite high imo |
Although Godot has been upgraded to version 4.2 and there are still no runtime constants that we can use, i think it is quite unreasonable to think that the What really worries me is that, as @Calinou said, "Immutable variables must also be initialized as soon as they're declared." Will this prevent us from defining immutable variables in the constructor method when creating an instance? What i mean is, one of the situations where such an immutable variable would be most useful would be as used in the code snippet below. class_name SomethingHasImmutableID
extends Object
let id: int
func _init(p_id: int):
id = p_id like in cpp class SomethingHasImmutableID {
private:
const int m_id;
public:
SomethingHasImmutableID(int p_id)
: m_id { p_id }
{}
}; If you let me think out loud, imho to achieve the effect here, it can be ensured that let variables can be defined as null, and then the first assignment can only be made if it is null, throw an error otherwise for sure. But the problem here is that initialization does not have to be done in the _init function. In fact, it is not necessary to do it at all. It is obvious that such a situation is unacceptable. Therefore, if it is known that a value is assigned to the let variable in the _init function, then the let variable can be allowed to be defined as null. |
Would be great to make it work with @export too, my use case would be like : class_name ResourceContainerBase
extends Object
@export readonly path: String
...
func create() -> ResourceContainerBase:
var ressource= preload(path)
...
return ressource
... class_name ResourceContainerImpl
extends ResourceContainerBase
func _init():
path= "const path inside godot" For now, preload work only only work on const variable that we can't associate with export (use it more for serialisation than edition), But when writing this message I supose maybe it would be complicated to make it, even if we have const inside variable, set it in children could be make the task too complicated. |
I feel like this is a weak argument. In JS, most do the immutable variable assignment with |
There are various languages with no I'd prefer something like
(You laugh, but Rust's closure syntax isn't exactly that similar to its function syntax when you see it taking a single expression with inferred types. |
I dont understand the argument,
This already doesn't work in GDScript because it does need explicit identifier whether it is a var or a function: func do_thing(foo: int):
print(str(foo))
var do_thing = func(foo: int):
print(str(foo)) will be caught by the LSP: Not sure how that relates to if a language is to use |
I think it makes a language more confusing if it borrows what, outside of JavaScript, are "100% synonymous... just in different languages" and assigns different sematics to them. Imagine what a hassle it would be if, as an English-French bilingual person, you looked at a codebase and saw both It also reminds me of situations like tripping over false friends when learning natural languages.
I firmly believe that it's a misdesign for a language to rely on IDE tooling to paper over shortcomings in the intuitiveness or human-friendliness of the syntax itself. Among other reasons, it makes it more hostile to users choosing a "use external editor" option that happens to not have an LSP host or for which they don't want to go through the hassle of setting up the LSP just yet. (eg. I habitually code Rust, Python, CSS, HTML, and JavaScript/TypeScript using a Vim that doesn't really give me anything beyond syntax highlighting and automatic paired tag insertion to prevent the highlighting from flickering when I type an opening character. For me, it's the coding equivalent of using a distraction-free writing tool like FocusWriter. It helps me to stay focused.) |
I understand the argument now. Yeah in that case,
This is Godot we are talking about. Regardless if you are using an outside editor or not, you still have to run the game from Godot, so no matter what, the tooling will exist. Moreover, you can just attach the LSP to any editor, it's simply running on a localhost port. For example, neovim's setup for Godot LSP is just as simple as attaching it like this reddit post showed.
Even then, you will still have to run your code inside Godot itself which will notify the (reassigning function as closure) error for you. It is a game engine for a reason. But yeah, I can see why |
Two different syntaxes which, in their respective languages of origin, are synonymous (function declaration in Python and C) but, in that hypothetical, encode a semantic difference.
You're assuming that...
(I have a Vim plugin that supports acting as an LSP host... I just generally keep it limited to format-on-save functionality because so much IDE/LSP functionality is distracting to me.) My point was that a language's syntax shouldn't assume a specific kind of relationship between the user and their static analysis tooling. |
I think I misunderstood the closure thing. I didn't realized you were comparing Python and C at first as I thought you were focusing on accidentally reassigning a function as a closure variable (which is why I gave that GDScript snippets and show you that LSP will catch it). That makes a lot more sense, and I think I agree with you now that
Regarding this point, let's agree to disagree. This is because I look at GDScript similar to a DSL where its usage is to be used with Godot engine and not as a generalized language (regardless of your editor the code still has to be run inside the engine, in which you will get the tooling anyway). However, that's only tangentially related to the current topic, so I'll end my reply here as to not clog the thread. (but basically I'm now favoring either |
I think we need to find a balance between clarity and convenience. In any case, you have to read the documentation to understand how the language works. This doesn't mean we shouldn't care about clarity and intuitiveness. However, if the alternative is too cumbersome and has other disadvantages, then it may make sense to sacrifice a little clarity. Especially in the case of keywords that are used frequently. In my opinion,
It's normal that keywords are used differently in different languages. There are tens of more or less popular languages in the world and thousands of little-known ones, we cannot avoid conflict with any language. The difference between Quote Calinou:
Why should we consider JavaScript more than Nim and Swift? |
Just some things to throw against the wall which would address "two keywords" part and the C# concern about
Because its position as the only language with direct access to the browser's DOM and the long period when it was the only non-plugin language to be runnable client-side has led to it being the most used programming language in the world (far more used than Nim or Swift) and one of the languages novice programmers get pointed to as their first language? |
I don't think this is a good reason to give it any more credence than any other language quite frankly (unless the goal is to make gdscript more like JavaScript, which it isn't and shouldn't be)... But I don't think we should give special credence to any language that didn't directly inspire gdscript anyway. JavaScript syntax just happens to rub me the wrong way in particular.
Edit: The only thing I really have a problem with is making it |
Note that Therefore, it would be weird for us to be inspired by the JavaScript solution that is due to historical reasons. It makes more sense to look at other languages. In fact,
The only alternative I see is |
If If this were a clean-slate situation, my personal favourite is |
How did this discussion go? (Is it still under consideration?) In Flutter, or rather in Dart, there are |
The naming part of the proposal is kinda irrelevant, whatever you choose, users will adapt, but this would be a great feature. Possible naming schemes would be etc |
In discussions about immutable variables, it seems that in C#, there are only read-only properties, but no immutable local variables. As a result, the discussions about read-only properties and immutable variables sometimes get mixed up. What I am looking for is immutable variables. They would provide hints when reading the source code, especially in cases where the value of a function is stored in a temporary variable, indicating whether the variable can or cannot be changed. (This is just one example.)
In most cases, it’s unlikely that the return value of a function will be modified. There’s no real need to store it in a mutable variable, but since immutable variables are not supported in the language, we are left with no choice but to use mutable variables. |
I didn't see anyone mention function parameters (sorry, thread too long). So I guess there's an argument for the annotation / prefix-keyword syntax as it would be consistent (using @onready @final var sprite = $Sprite
func foo(@final node: Node) -> void:
@final var parent = get_parent() I'm not saying this is better. I really like no-prefix @onready let sprite = $Sprite
func foo(let node: Node) -> void:
let parent = get_parent() Still, perhaps it's worth considering a combination of both? @onready readonly var sprite = $Sprite
func foo(readonly node: Node) -> void:
val parent = get_parent() or @onready @final var sprite = $Sprite
func foo(@final node: Node) -> void:
val parent = get_parent() |
The way I see it:
|
And... there's also @onready def sprite = $Sprite
func foo(def node: Node) -> void:
def parent = get_parent()
I think |
I posted a pull request to mark variable declarations as immutable with "let". #99778 I have a question. How should we deal with mutability of function parameters? Ideally, I'd like function parameters to be immutable by default, but I don't want to make this a breaking change of course. How about we reuse the let syntax in function parameters to mark them as immutable:
|
I guess it's better than using some annotation. And I doubt a prefix like |
You can already do |
@Calinou Really? In master? I get an error in 4.3 |
Nevermind, maybe it was possible in 3.x. |
Describe the project you are working on:
video game
Describe the problem or limitation you are having in your project:
I can't enforce a varriable only being assigned once
Describe the feature / enhancement and how it helps to overcome the problem or limitation:
I propose adding a new keyword to GDScript:
val
in addition to the existingvar
for declaring variables. This would force the variable to only be assigned once.Kotlin, ECMA6, and Swift all have a feature similar to this.
Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
declaring a variable as
val
will throw a error if the variable is assigned more than once. It allows for a few nice conventions:If this enhancement will not be used often, can it be worked around with a few lines of script?:
Can't be worked around
Is there a reason why this should be core and not an add-on in the asset library?:
Can't, it's a language level feature
The text was updated successfully, but these errors were encountered: