Skip to content
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

Properties in GDScript #844

Closed
vnen opened this issue May 15, 2020 · 152 comments
Closed

Properties in GDScript #844

vnen opened this issue May 15, 2020 · 152 comments
Milestone

Comments

@vnen
Copy link
Member

vnen commented May 15, 2020

Describe the project you are working on:
The GDScript compiler.

Describe the problem or limitation you are having in your project:
The current way to define setters and getters are to use the setget syntax and name the functions to use as setter and getter.

There are a few problems with such syntax:

  1. If you only want to use a getter, you need to have a weird dangling comma, which is easy to miss: var x setget ,get_x
  2. Together with type and default value, the declaration line is already too long (export and `onready will move to annotations which improves this but not completely).
  3. You have to separate the functions from the variables, so they look disjoint while they should be a cohese block.
  4. You have an extra function in your script that's not always clear, from the user of the script, whether they should call the set_x() function or just set the x field with the automatic setter.
  5. Setters and getters aren't called if you use the variables inside the same class, which gives confusing semantics and makes it hard to refactor.
    • E.g. if you are internalizing a previously external function, you have to be careful to actually call the getter/setter when using member variables.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
Implement a syntax for actual properties instead of setget.

Moving setget to annotations would solve (2) but not the others (although (5) is independent of syntax, we could fix it while keeping the keyword).

With properties we can keep a cohese block with variable declaration together with setter and getter, without exposing a extra functions.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
This is one possible way of declaring a property:

var milliseconds: int = 0
var seconds: int:
    get:
        return milliseconds / 1000
    set(value):
        milliseconds = value * 1000

So the getter and setter are included in the variable declaration as extra blocks, so no function declaration needed. Type specifiers are not needed also since the variable is typed and the setter/getter will enforce the same type.

Alternatively, we could use a new property keyword, but I don't feel it's needed. It's visually clear that the blocks belong to the variable.

You can also set only the getter for a read-only property:

var start: int = 0
var end: int = 0
var interval: int:
    get:
        return end - start

To avoid creating external variables for this, you can use a generated member variable inside the setter and getter:

signal changed(new_value)
var warns_when_changed = "some value":
    get:
        return warns_when_changed
    set(value):
        changed.emit(value)
        warns_when_changed = value

Edit: I think the proposal I made in #844 (comment) could be a compromise for people who still wants actual functions as setter/getter:

So, I guess we can provide a special syntax if you still want a separate function:

var my_prop:
    get = get_my_prop, set = set_my_prop

func get_my_prop():
    return my_prop

func set_my_prop(value):
    my_prop = value

If this enhancement will not be used often, can it be worked around with a few lines of script?:
It will be used often and the current way makes this more complex to write and maintain.

Is there a reason why this should be core and not an add-on in the asset library?:
It cannot be implemented as an addon.

@arkology
Copy link

You have to separate the functions from the variables

I should disagree. This is absolutely not a problem. It allows keep code in separate blocks: first variables and then functions. Which is great as it keeps code clean and as readable as possible.
But suggested way to declare properties also looks good...

@vnen
Copy link
Member Author

vnen commented May 15, 2020

I should disagree. This is absolutely not a problem. It allows keep code in separate blocks: first variables and then functions. Which is great as it keeps code clean and as readable as possible.
But suggested way to declare properties also looks good...

Note that I mean specifically the getters and setters. If you see a setget in a variable, you need to scroll down and find where the function is defined to see what it does.

With the syntax proposed here, I would split the blocks as: variables, properties, then functions.

@groud
Copy link
Member

groud commented May 15, 2020

Why not use annotations for that ? I think I would prefer something like:

@getter(get_myproperty)
@setter(set_myproperty)
var myproperty

@vnen
Copy link
Member Author

vnen commented May 15, 2020

Why not use annotations for that ? I think I would prefer something like:

@getter(get_myproperty)
@setter(set_myproperty)
var myproperty

I mentioned annotation as an alternative, but it doesn't solve points (3) and (4). It also would make difficult to solve (5) since the setter/getter needs to access the variable without the setter/getter, but if it's a regular function then the conditions for direct access get more complex.

@groud
Copy link
Member

groud commented May 15, 2020

For me, (3) is not really a problem as in most cases, the setter and getter will have very name very close to the variable name, so I don't think it is that confusing. For point (4), I am not sure there is way to solve this problem, unless by adding more explicit keywords maybe, but that would be weird.

Regarding point (5), I think we can call the getter/setter using self.myproperty no ? This kind of make sense to me I believe (though it may be cause problems when you use self to bypass a scope-limited variable).

@vnen
Copy link
Member Author

vnen commented May 15, 2020

Regarding point (5), I think we can call the getter/setter using self.myproperty no ? This kind of make sense to me I believe (though it may be cause problems when you use self to bypass a scope-limited variable).

Yes, it is possible to use self.myproperty, but then you have to be conscious of it all the time to avoid mistakes. If you refactor the code to a getter or setter, you'll need to check every place to add the self. notation. And if you are removing them, you need to remove the self. as well, otherwise you are losing in performance for no gain.

I've dealt with many Godot beginners who were baffled by this behavior and found it confusing. So no matter what syntax we pick, I believe (5) must be resolved.

@vnen
Copy link
Member Author

vnen commented May 16, 2020

For annotation or other syntax, it's important to keep in mind the whole context when evaluating. Of course two/three lines of notation may seem cleaner but you need to take in account that those are defined somewhere.

So an annotation syntax would look more like this:

var milliseconds: int = 0
@getter(get_seconds)
@setter(set_seconds)
var seconds: int

# [...]  a bunch of other code here

func get_seconds():
    return milliseconds / 1000

func set_seconds(value):
    milliseconds = value * 1000

Which in my view requires more boilerplate code than it needs, by exposing two functions that most likely won't be used, because why would you need to use them if using the property is simpler? A similar happen with the engine native getters and setters when properties were introduced (though they still work, they're not exposed in the documentation).


This example code also can illustrate the issue (5). Anywhere in the class if you use self.seconds it has a different meaning than simply seconds. If you forget this detail you can be surprised by a subtle bug that you can easily miss. Especially because it's more common to not use self for this.

And you also now have a member variable that you don't ever use (which could be stripped out by a property syntax, recognizing it's not used).

@hilfazer
Copy link

var start: int = 0
var end: int = 0
var interval: int:
    get:
        return end - start

If that is a read-only property then how to define a variable with custom getter but not setter, that allows other scripts to do a direct assignment like it is currently possible?

otherNode.interval = 42
Above code does direct assignment if no setter is provided for interval.

@aaronfranke
Copy link
Member

Why have set(value):? There will always be one argument. Why not make value implicit, such that the line looks like set: and value is still defined? This is how C# does it.

@Shadowblitz16
Copy link

if setget is seperated into set: and get: I would really like to be able to use one liners...

export(float) var my_value set: set_my_value, get: get_my_value

it would also be nice to beable to switch order of set and get

export(float) var my_value get: get_my_value,set: set_my_value

@arkology
Copy link

Why not make value implicit, such that the line looks like set: and value is still defined?

As for me I always name argument in set functions in different ways. Not always value.

it would also be nice to beable to switch order of set and get

Yes, I agree with it.

@hilfazer
Copy link

Why have set(value):? There will always be one argument. Why not make value implicit, such that the line looks like set: and value is still defined? This is how C# does it.

Because

Explicit is better than implicit.

What C# does should not matter for GDScript design given it was added for people who didn't want to learn/use GDScript. I'm for explicit value since that's what Python does.

@vnen
Copy link
Member Author

vnen commented May 17, 2020

@hilfazer

If that is a read-only property then how to define a variable with custom getter but not setter, that allows other scripts to do a direct assignment like it is currently possible?

otherNode.interval = 42
Above code does direct assignment if no setter is provided for interval.

That's something to discuss. We can either make it read-only if there's no setter, or use direct assignment (then use a @readonly annotation for this).


@aaronfranke

Why have set(value):? There will always be one argument. Why not make value implicit, such that the line looks like set: and value is still defined? This is how C# does it.

Because I despise implicit identifiers in scope. Say your class has a property named value, how do you define a setter for it?

var value:
    set:
         self.value = value

In this case, self.value will call the setter recursively, which is bad. Sure we can detect this pattern and compile it correctly, but I prefer to simply error out.

Also, naming it something else can make your code clearer to read.

Explicit is better than implicit.

I think this saying is overused. But I also think that removing magic is better for understanding.

@Shadowblitz16

if setget is seperated into set: and get: I would really like to be able to use one liners...

export(float) var my_value set: set_my_value, get: get_my_value

This is a different idea. In my proposal you won't need to define the functions elsewhere, so this is notation is not really useful. I'm not "splitting setget", I'm defining a new way of doing setters and getters.

If we went that route I'd prefer the annotation syntax. Otherwise it wouldn't solve point (2) in my original post.

it would also be nice to beable to switch order of set and get

export(float) var my_value get: get_my_value,set: set_my_value

As said, this notation won't work, but in my proposal order doesn't matter.

@Shadowblitz16
Copy link

@vnen I really don't know about this.
I dislike the idea of having to explicitly define value and I think functions make the code look alot nicer.

  • annotation should not be used for everything, it makes things more confusing and code less readable
  • defining logic inside the property declaration is fine but I would still like to be able to use functions for long setters and getter logic.

If this has to do with people calling the getter and setter functions then I think some sort of equivalent of the private keyword should be used.

@Error7Studios
Copy link

Error7Studios commented May 17, 2020

With this change, if I want to directly set a variable inside the class that defined the variable (without calling its setter function), would I still be able to do that?
(Like using my_var = value instead of self.my_var = value)

If not, it would cause a stack overflow in situations where 2 variables have setters which set the values of each other.

For example, say I have a turn-based game.
Whenever I change the current player, I also need to change the current opponent.
By not using self when setting the variable, I can do it like this with no problem:

const PlayerName := {A = "Player A", B = "Player B"}
const Key := {Player = "Player", Opponent = "Opponent"}

var c_player_name: String = PlayerName.A setget set_c_player_name
var c_opponent_name: String = PlayerName.B setget set_c_opponent_name

func set_c_player_name(__player_name: String):
	set_c_player_or_c_opponent_name(Key.Player, __player_name)

func set_c_opponent_name(__opponent_name: String):
	set_c_player_or_c_opponent_name(Key.Opponent, __opponent_name)

func set_c_player_or_c_opponent_name(__key: String, __player_name: String):
	assert(PlayerName.values() == [PlayerName.A, PlayerName.B])
	assert(PlayerName.values().has(__player_name))	
	assert(Key.values() == [Key.Player, Key.Opponent])
	assert(Key.values().has(__key))
	
	var __player_name_index: int = PlayerName.values().find(__player_name)
	var __other_name_index: int = abs(__player_name_index - 1)
	var __other_player_name: String = PlayerName.values()[__other_name_index]

	if __key == Key.Player:
		c_player_name = __player_name
		c_opponent_name = __other_player_name
	else:
		c_player_name = __other_player_name
		c_opponent_name = __player_name
		
	assert(c_player_name != c_opponent_name)

If not, maybe a new function/annotation or something would be needed, like:
setd(<var name>, value)
(In this case, setd would stand for "set directly", and would not call the setter function.)

@aaronfranke
Copy link
Member

@vnen Because I despise implicit identifiers in scope. Say your class has a property named value, how do you define a setter for it?

Simple, you disallow having a property named value. The name is too generic to be clear, and you already can't declare properties named var or func or extends.

@vnen
Copy link
Member Author

vnen commented May 18, 2020

@Shadowblitz16

I dislike the idea of having to explicitly define value and I think functions make the code look alot nicer.
I don't understand because if you are using functions, then you definitely need to define the value parameter.

  • annotation should not be used for everything, it makes things more confusing and code less readable

I agree they shouldn't be used for everything (and they aren't) but in this case I find much more confusing and less readable to have all in the same line. Longer lines are always harder to read. And annotations can go in the same line if you so prefer.

  • defining logic inside the property declaration is fine but I would still like to be able to use functions for long setters and getter logic.

Well, with my proposal it's not impossible to do it:

var my_prop:
    get: return get_my_prop()
    set(value): set_my_prop(value)

The only thing is that in this you would need a backing field for use inside the setter and getter, to avoid an infinite loop.

So, I guess we can provide a special syntax if you still want a separate function:

var my_prop:
    get = get_my_prop, set = set_my_prop

Order doesn't matter, and both are optional. With this we can detect the pattern and allow direct access inside the functions (though I still don't like providing direct access far from the declaration, but it's a compromise I could accept).

@vnen
Copy link
Member Author

vnen commented May 18, 2020

@Error7Studios

With this change, if I want to directly set a variable inside the class that defined the variable (without calling its setter function), would I still be able to do that?
(Like using my_var = value instead of self.my_var = value)

No. That's the problem (5) that I mentioned in the original post and it is a problem I intend to fix. If you need direct access in this case, you can use a backing field.

If not, maybe a new function/annotation or something would be needed, like:
setd(<var name>, value)

I guess we could have a way to edit the field directly, but I'm not convinced yet that it's really needed.

@vnen
Copy link
Member Author

vnen commented May 18, 2020

@aaronfranke

@vnen Because I despise implicit identifiers in scope. Say your class has a property named value, how do you define a setter for it?

Simple, you disallow having a property named value. The name is too generic to be clear, and you already can't declare properties named var or func or extends.

That is a super bad name to disallow. Is very unlikely you'll name something var or func (even extends. And if we are to allow using actual functions as setter, you're likely to use value as the parameter.

What we could do, to settle on a compromise, is make the setter parameter optional and assume it's value when not present. I still don't like implicit identifiers, but I guess in this case is passable.

@profan
Copy link

profan commented May 19, 2020

I'd really prefer it if you don't introduce two ways of doing the same thing unless really necessary, if they want the C#-ite semantics just use C#, being explicit is good and building in exceptions already seems like a poor idea.

Since it functionally fulfills everyones requirements with the original (explicit, simple) proposal and people can just call their function in the setter/getter if they want I don't see the problem. (i think $ in gdscript already confuses people enough though, so i may be extreme)

... Just my two cents anyways 👀

@dalexeev
Copy link
Member

Problem (4) is not significant because:

200520-1

x.property is most often equivalent to x.get_property(), and x.property = value to x.set_property(value). GDScript design must be consistent with Godot design.

@vnen
Copy link
Member Author

vnen commented May 20, 2020

@dalexeev honestly I would remove that from the docs. I would even make those methods inaccessible from scripting. They are not needed, exactly because you can simply set those as properties. I don't see one reason to use the method instead of the property.

@dalexeev
Copy link
Member

I don't see one reason to use the method instead of the property.

Non-obvious use cases are possible. For example, using funcref. Something like:

var action = funcref(npc, "set_position")

@vnen
Copy link
Member Author

vnen commented May 20, 2020

Non-obvious use cases are possible. For example, using funcref. Something like:

var action = funcref(npc, "set_position")

Well, you can create your function if you really need this. Especially when lambdas are added it will likely be simple to do it in the same line.

@marcospb19
Copy link

(5) Setters and getters aren't called if you use the variables inside the same class

(5) isn't an issue and should not be touched, instead, tutorials should notify that setters and getters are triggered when attributes are accessed by scripts outside of the class. It is very common to access a variable from inside without wanting to trigger a method for outside access. The very-known objective of setters and getters is hiding the complexity from outside of the class, not from inside. This "difficulty" beginners might experience is part of understanding scripting fundamentals, the tutorial should explain it, we do not need to reinvent a new weird behavior that would make people even more confused.

Yes, it is possible to use self.myproperty (to access without calling the getter), but then you have to be conscious of it all the time to avoid mistakes.

Yeah, you have to be conscious of what you're typing, there are two ways of accessing, and you should know which one of those two you want to use. People need to understand that self has a meaning, and it is a feature, you shouldn't write self without being conscious of what it means.


Besides that, I like this change 👍, easy to parse and looks like a very useful shortcut for typing setters and getters. If this is to be implemented, we should also discuss setget's future and usefulness.


You have to separate the functions from the variables, so they look disjoint while they should be a cohesive block.

I totally agree with this, there's no point in separating variables from it's setters and getters.

@Shadowblitz16
Copy link

Shadowblitz16 commented May 21, 2020

@vnen
I still don't think value should be explicit.
we all know it passes a value and only 1

var my_prop:
    get: return get_my_prop()
    set(value): set_my_prop(value)

should be..

var my_prop:
    get: return get_my_prop()
    set: set_my_prop(value)

also i like the idea of doing something like...

var my_prop:
    get := get_my_prop
    set := set_my_prop

or something similar.
its basically C# linq which looks nice

@marcospb19
Copy link

marcospb19 commented May 21, 2020

@vnen
I still don't think value should be explicit.
we all know it passes a value and only 1

With all the respect, this is a very bad idea, click on this comment it already has 6 dislikes.

There are three main reasons for this reaction:

  1. Making value implicit would add another unnecessary reserved word, and nobody wants that.
  2. We wouldn't be able to customize the name of the parameter the function receives (like _process requires us to type delta or rename the variable, delta is not a reserved word!).
  3. Types (please support static typing for this).

@hilfazer
Copy link

In order to expand on "C# does it" I did a quick research (please correct me where i'm wrong) about setters in other languages. I've checked languages that were considered to use for scripting in Godot plus Haxe. My results:

Explicit setter's value: Lua, Python, Squirrel, JavaScript, ActionScript, Haxe
Implicit setter's value:

Personally i consider implicitness of setter's value a small issue which does not justify reserving a word.

@Shadowblitz16
Copy link

@marcospb19

  1. value would only be a new keyword inside a setter it wouldn't even be highlighted outside of a setter

  2. there is no need to customize the name of the value because its just that a value.

  3. the example wasn't just a static typing example but also a linq style example where you could pass a setter and getter function to it without specifying value or a return on either side

implicit is better for this case because again there is only one thing your passing a value.
if this needs to be a option go for it I just hate defining something everyone knows what it is

@Giwayume
Copy link

@vnen

However I don't feel it's needed and it's just blind future-proofing. After people start using this and they really see it as as problem that is hindering code writing/reading or productivity, then we can consider adding it.

This proposal basically works the same as ES6 getter/setters, except in Javascript there is no such thing as "direct access" to the property (inside of the getter/setter function, or anything, like the current proposal), essentially if a property is defined as a getter/setter, it can no longer contain a value. The property is an alias to two functions at that point.

I've worked with Javascript for quite a while now, and have never been a fan of having a 2nd backing variable unless the getter/setter is truly a combination of multiple existing variables, as opposed to a way to run some extra logic before setting a variable's value. This discussion isn't novel or a problem unique to GDScript.

Essentially it becomes a problem of coming up with a different variable name for your internal data vs external interface, and a lot of times you want that name to be the same. Finding alternate names that make sense for variables when you could've had the option to just reuse the same name would save a lot of time.

The alternative, like suggested, is just use "_" + variable_name. But here's the problem with that, it's only viable if you do it for literally every property. Otherwise it creates an inconsistency where coming back to the code a month later you wouldn't know off the top of your head which variables have "_" backing variables and which do not. Consistency is key to having a well human-maintained program where the priority is to avoid unnecessary mistakes.

So basically, I've been dealing with this issue for a long time now in other languages, and my solution is to come up with an alternate name for any variable that needs a different external interface. That's a lot of extra cognitive load to writing a program that really doesn't need to be there if the language just supports direct access from inside of the class.

@aaronfranke
Copy link
Member

aaronfranke commented Sep 14, 2020

But here's the problem with that, it's only viable if you do it for literally every property.

I would argue that many properties don't need direct access to a backing variable (and also most members in general can just be fields with no getter/setter logic), so no, you don't need this for literally every property.

Also, even if you have a backing variable, you shouldn't use it from outside the script it's declared in, because the point is for the property to be the public interface. Therefore it's not important to memorize which properties have backing variables.

@vnen
Copy link
Member Author

vnen commented Sep 14, 2020

This proposal basically works the same as ES6 getter/setters, except in Javascript there is no such thing as "direct access" to the property (inside of the getter/setter function, or anything, like the current proposal),

As I said, in this proposal you do have direct access inside the getter/setter. So you don't need a backing field unless you need direct access somewhere else.

@Giwayume
Copy link

Giwayume commented Sep 14, 2020

@vnen

As I said, in this proposal you do have direct access inside the getter/setter. So you don't need a backing field unless you need direct access somewhere else.

Yeah, that is exactly what I was stating here:

except in Javascript there is no such thing as "direct access" to the property (inside of the getter/setter function, or anything, like the current proposal)

The purpose of that sentence was to point out how this is different from Javascript. I understand how it works.

@aaronfranke

I would argue that many properties don't need direct access to a backing variable (and also most members in general can just be fields with no getter/setter logic), so no, you don't need this for literally every property.
Also, even if you have a backing variable, you shouldn't use it from outside the script it's declared in, because the point is for the property to be the public interface. Therefore it's not important to memorize which properties have backing variables.

You misunderstand me bro. Nowhere did I say the backing variable would be used outside of the class. I'm talking about organization of the class itself. If I had a variable "x" that needed backing but "y" did not, it makes no sense to be calling "_x" then "y" in the code to set both. This kind of thing would be an anti-pattern and a naming convention nightmare.

Basically, this backing variable idea is dooming every variable used internally in the class to be prefixed with _, where only public facing properties & getters and setters are not. This further complicates things such as local variables inside functions. Either those are prefixed with _ as well or you have this weird mix where local variables & a public property getter/setters look like the same thing from a naming convention perspective, and internal class properties look out of place.

@Giwayume
Copy link

Basically, if I'm going to prefix variables with _, it has to mean something. You don't change your naming convention on the fly because the situation demanded it. That is bad design.

@vnen
Copy link
Member Author

vnen commented Sep 14, 2020

The _ prefix is already a convention for private variables so it's nothing new. When you access a variable starting with _ you know it's a private value while when it doesn't have it you are accessing a public value (and the setter/getter that goes with it).

@Giwayume
Copy link

The _ prefix is already a convention for private variables so it's nothing new. When you access a variable starting with _ you know it's a private value while when it doesn't have it you are accessing a public value (and the setter/getter that goes with it).

Sure, but you're rewriting the language, and what happens when #641 goes in? _ to indicate private variables is only used in languages that do not have actual access modifiers.

@vnen
Copy link
Member Author

vnen commented Sep 14, 2020

Sure, but you're rewriting the language, and what happens when #641 goes in? _ to indicate private variables is only used in languages that do not have actual access modifiers.

GDScript doesn't have access modifiers. That one is a community proposal we don't know yet if that's gonna be done or not.

I still prefer a difference between x and _x than a difference between x and self.x. Again, once this is released if users find it a real problem we consider a special syntax for direct access. The experience with JavaScript doesn't tell me much because usage is different between languages. I feel this is not used often enough to justify this yet.

@Giwayume
Copy link

The experience with JavaScript doesn't tell me much because usage is different between languages. I feel this is not used often enough to justify this yet.

It's just weird, man. Because at least Javascript has a set design philosophy, you cannot store a value in a getter/setter variable, it must be stored in a second variable. Whereas the GDScript proposal does allow you to store the value with the getter/setter, but only in certain use cases. Honestly if it worked like Javascript where you couldn't store the value and were forced to use a second variable all of the time I wouldn't have as much a problem with it, right now I'm getting mixed messages from this design.

@Shadowblitz16
Copy link

I agree properties should only be getters and setters they should not hold data of any kind just refrence it

@strank
Copy link

strank commented Sep 14, 2020

@vnen if the "default backing field", the one with the same name as the property, is never accessed in the setter*, is it planned to avoid initializing it. So there is no unused, potentially large, object allocated?

* (or in the getter, I guess, but accessing it only there would be a bug, I'd say, and should maybe be flagged with a warning.)

@vnen
Copy link
Member Author

vnen commented Sep 17, 2020

@vnen if the "default backing field", the one with the same name as the property, is never accessed in the setter*, is it planned to avoid initializing it. So there is no unused, potentially large, object allocated?

Yes, the space is allocated only if you use the variable.

@jejay
Copy link

jejay commented Sep 17, 2020

Honestly if it worked like Javascript where you couldn't store the value and were forced to use a second variable all of the time I wouldn't have as much a problem with it, right now I'm getting mixed messages from this design.

Its the same for python, C#. I think you really can't solve problem 5 while having these weird "maybe" generated variables. Because you would just invert the confusion as long as there are ways to use x = as direct access variable and as a property. Every programming language that has properties that I know of uses the name of the property exclusively as syntax sugar for getter/setters, never is any value actually stored under that name. If you try to use the property's name as a variable in the property definition, good luck. This way it is clear that you always mean property access, because nothing else exists.

This is the only real "consistency" that you can have. I strongly disagree when @vnen calls it consistent behaviour when its sometimes direct access and sometimes property access, even if its "consistently" sometimes this and sometimes that. You still have to remember this which is quite hard if you had exposure to properties in any other programming language. I think just explaining these two different variants to anyone who has never heard anything about properties is a nightmare, more so than explaining the direct access case to someone who is "only" familiar with properties in every other language.

Further, when I had just stumbled upon properties in GDScript while reading code and seeing both ways of doing it -- assuming that I would actually be able to infer the behaviour -- I would probably have assumed that the way with backing variables is a hacky misuse and that there is now an allocated but unused variable. I would probably refrain from using it with backing variables because I don't like hacky misuses.

I think this is why all the other languages force @Giwayume to come up with different internal/external names. Its just way simpler to understand and keep track of. And if you at one point really want to change just the variable directly just use _x = or whatever you use as your internal variable naming convention. We are all consenting adults here, as Guido van Rossum once said. But at least its easy doable and very obvious. Not so if you have defined the property with that auto-generated shadowed variable.

The first syntax (in the first two blocks of @vnen 's initial post) is the only one that makes sense to me in this context. But my taste is that it should look more like a function, because it is syntax sugar for functions. So I would prefer dead simple:

var milliseconds: int = 0

get seconds: int:
        return milliseconds / 1000

set seconds(value: int):
        milliseconds = value * 1000

But this is just my personal taste.

Edit: Forget my taste. This syntax would allow you to do weird stuff like

var x_i: int = 0
var x_f: float = 0

get x: int:
        return x_i

set x(value: float):
        x_f = value

I don't know if its necessary to allow this. Probably just @vnen ' s proposal minus the awkward generated variables (;-)) is the better solution.

@Shatur
Copy link

Shatur commented Sep 21, 2020

After godotengine/godot#40598 is setget always called inside the same class to solve (5)? This is a bit limiting.
For example, I have a constructor for a UI element that takes 2 objects and updates its text after that depending on their value only once.
But when I access any such object of this class from the outside, then I update automatically update UI text.
So, after 4.0 my constructor will trigger text change twice. I can avoid it by adding boolean variable, but it will not very readable.

@aaronfranke
Copy link
Member

@Shatur95 In your particular case, I see that you have both resize and set_team_number calling _update_text, and both of those are called in the constructor. If you need all of those to stay the same, you can just use a backing field. But I'm a bit confused, with your current code, wouldn't _update_text never run if typeof(slots) == TYPE_INT_ARRAY?

@Giwayume
Copy link

Giwayume commented Sep 21, 2020

@Shatur95

The proposed solution here is this:

var _team_number: int
var team_number: int:
    get:
        return _team_member
    set(number: int):
        _team_number = number
        _update_text()

func _init(tree: Tree, number: int, slots) -> void:
    _team_number = number
    # The rest of your constructor

@aaronfranke

But I'm a bit confused, with your current code, wouldn't _update_text never run if typeof(slots) == TYPE_INT_ARRAY?

I think their personal coding problem is off topic here, Though I realize this thread is very long already.

@Shatur
Copy link

Shatur commented Sep 21, 2020

If you need all of those to stay the same, you can just use a backing field.
The proposed solution here is this:

Thanks the tip about backing field. The properties syntax should improve the readability of the code, but for me personally, the option with a backing field looks less readable.
It was a little more convenient in 3.2, because I could change the values ​​of variables without calling the setter directly or just call setter function (or via self) when needed.

Off topic

But I'm a bit confused, with your current code, wouldn't _update_text never run if typeof(slots) == TYPE_INT_ARRAY?

Oh, yes, thanks. I just forgot to add _update_text() after loop. Just not tested this condition for now.

@vnen
Copy link
Member Author

vnen commented Oct 1, 2020

We discussed this in a meeting with the core devs and since this is already implemented with godotengine/godot#40598, then it can be closed.

Note that the implementation took in consideration the discussion here, by finding a compromise with differing opinions.

Discussion can still happen here. Any idea for a big change in the syntax/behavior should be made as a new proposal.

@vnen vnen closed this as completed Oct 1, 2020
@cgbeutler
Copy link

Is there docs somewhere for how it was implemented?

Before I get into concerns I want to say that I do like the direction this feature is going.

I see some potential confusion/issues with initialization. In most languages with properties, the backing field and property have separate initializers so you can choose whether to go through the setter or not. Example:

var _foo: int = 0  # < Initializing backing field. Setter not called
var foo: int:
  ... #property stuff

var _foo: int
var foo: int = 0:  # < Initializing property. Setter is called
  ... #property stuff

Usually, having the backing field and property separate don't matter. With gdscript, however, we have @export to worry about.
Under the old solution, the backing field and the property were tied together in a way that export could get extra data, like default values. Breaking that tie gets makes exports a little confusing (which, again, most languages don't have to worry about.)

The second use-case from above is fine, but the first use case from above gets confusing.

var _foo: int = 0  # < Initializing backing field. Setter not called... the first time
@export var foo: int:  # < Can't tell default val. Val always saved, thus setter always runs after first save.
  ... #property stuff

I'm not sure if there are other implicit issues with breaking the tie. Also, I know this isn't a super big issue. I just could see folk's assumptions about other languages leading to confusion as to why the setter is getting called, which leads to issues being opened and stuff. Getting ahead of it by making sure the docs are good would be... good.

@joemicmc
Copy link

Are there any examples of how to use this? Doesn't look the docs have been updated.

@joemicmc
Copy link

Okay I figured it out. I was trying to get it to work using export annotation:

var _resolution: Vector2
@export var resolution: Vector2:
    get: 
        return _resolution
    set(value):
        _resolution = value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests