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

Add better dynamic typing support for gdscript #3296

Closed
Shadowblitz16 opened this issue Sep 14, 2021 · 20 comments
Closed

Add better dynamic typing support for gdscript #3296

Shadowblitz16 opened this issue Sep 14, 2021 · 20 comments

Comments

@Shadowblitz16
Copy link

Shadowblitz16 commented Sep 14, 2021

Describe the project you are working on

Space ship game.

Describe the problem or limitation you are having in your project

Setting values on a node without needing a script on it or knowing what type it is.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I love statically typed languages, but this is something GD script is not and never fully will be.

I suggest allowing something like this to be done..

#weapon code
func _on_timer_timeout():
  var bullet = bullet_scene.instance()
  bullet.damage = damage #bullet.damage may not exist.
#bullet code
func _area_entered(area):
  if damage: # check if damage exists
   body.health -= damage

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

This is a two step suggestion.

  • Allow declaring new variables on objects with the . accessor
  • Undefined variables are treated as null by default.

This allows..

  • Setting a variable on a object of unknown type.
  • Getting a variable on a object of a unknown type.
  • Easier and more coherent way of checking if a variable exists

Note undeclared variables do not show up in inspector.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Not really we have static typing but it poorly supported right now and requires a script to be attached to the node.

Is there a reason why this should be core and not an add-on in the asset library?

Other dynamically typed languages allow this.

@YuriSizov
Copy link
Contributor

YuriSizov commented Sep 14, 2021

Not really we have static typing but it poorly supported right now and requires a script to be attached to the node.

Though it does indeed require a script to be attached to the node, you can already do that with _set and _get overrides.

@Shadowblitz16
Copy link
Author

Though it does indeed require a script to be attached to the node

The main point of my suggestion is this would be either part of object or node

you can already do that with _set and _get overrides.

set and get don't do anything if the variable is undeclared

@Calinou
Copy link
Member

Calinou commented Sep 14, 2021

I would recommend sticking to Object's get("property") != null, get("property") and set("property", value) to dynamically set properties. Note that Object's set() does not allow creating new properties in existing objects. Instead, use Object metadata if you need to create new properties in arbitrary objects too (has_meta()/get_meta()/set_meta()).

@YuriSizov
Copy link
Contributor

set and get don't do anything if the variable is undeclared

You can implement whatever logic you want with _set and _get. Variables don't need to exist as properties, you can store them internally whichever way you like. Along with _get_property_list you can also make them appear in the Inspector or be serialized to the disk when saving the scene.

The main point of my suggestion is this would be either part of object or node

Well, the question was "If this enhancement will not be used often, can it be worked around with a few lines of script?", so I'm clarifying that yes, yes it easily can be.

@Shadowblitz16
Copy link
Author

Shadowblitz16 commented Sep 14, 2021

This can't be worked around without a script though that's my point.
You have to attach the script to the object if you want to tag it with a value.
You also have no way of knowing what type it is anyways, currently godot doesn't allow you to export typed packed scenes so what your spawning is unknown.
And type checking everything is a pain.
Its much easier to set a value on a object and implement the logic in the object itself, so if the object doesn't contain logic for damage then it doesn't do anything but if it does no type checking needed only the variable check in the object itself.
Also this allows for a understandable syntax of =, . and optional != null
The best part is you can use it externally too you can have a enemy that requires a damage variable to be set on the bullet when it hits it, else it does nothing.

@YuriSizov
Copy link
Contributor

You also have no way of knowing what type it is anyways

Your proposal is all about setting values of unknown types. I have no idea what PackedScenes have to do with this though, as PackedScenes are not a part of GDScript syntax.

You have to attach the script to the object if you want to tag it with a value.

If you only want to tag the object with a value, use meta, like Calinou has suggested.

Its much easier to set a value on a object and implement the logic in the object itself, so if the object doesn't contain logic for damage then it doesn't do anything but if it does no type checking needed only the variable check in the object itself.

That sounds like a lot of opportunities to create bad code and hard to catch errors. We can't stop people from shooting themselves in the foot, but we shouldn't make it trivial to do so.

Keep in mind, that GDScript is dynamically typed, but it's not weakly typed.

@Shadowblitz16
Copy link
Author

Shadowblitz16 commented Sep 14, 2021

Your proposal is all about setting values of unknown types. I have no idea what PackedScenes have to do with this though, as PackedScenes are not a part of GDScript syntax.

The PackedScene of the bullet when spawned is unknown. it could be Bullet it could be SomeTypeThatDoesn'tWork,
you don't know until you type check it. which is unnecessary,
even so its not known at compile time its only known at runtime.
You can't statically type signals or packed scenes which leaves us without a way of a strongly and statically typed connection.

If you only want to tag the object with a value, use meta, like Calinou has suggested.

why though that's what variables are for.
also its much more work to do something like that.

That sounds like a lot of opportunities to create bad code and hard to catch errors. We can't stop people from shooting themselves in the foot, but we shouldn't make it trivial to do so.

Not bad code lua does it, java script does it. its a way of making sure something is there for the objects to use later.
Gamemaker also allows it and its known to be one of the easiest game engines to use.

Keep in mind, that GDScript is dynamically typed, but it's not weakly typed.

Dynamically typed means not statically typed which means not compile time safe,
you can still strongly type it doing something like..
body.health -= damage : float
also even if you don't it defaults to variant so its still strongly typed

@Shadowblitz16
Copy link
Author

Shadowblitz16 commented Sep 14, 2021

I guess this feature is a combination or variables and meta data.
TBH it would probably remove some bloat from Godot because you would only have one system instead of two.
GDscript is already a scripting language of a Godot engine api and from what I understand C# and c++ are going to become extension languages and GDscript with remain a scripting language. so I don't see how this could break compatibility with 4.0, especially since 4.0 already breaks a ton of stuff.

@YuriSizov
Copy link
Contributor

why though that's what variables are for.
also its much more work to do something like that.

bullet.damage = 3 vs bullet.set_meta("damage", 3) is exactly the same amount of work. But the first one removes any expectations of a type, which everything in GDScript has, even if implicitly, and the second is safe.

@dalexeev
Copy link
Member

What if we add a shortcut for metadata? bullet.metadata.damage or bullet.meta.damage? Essentially, metadata is a dictionary.

@AaronRecord
Copy link

AaronRecord commented Sep 14, 2021

What if we add a shortcut for metadata? bullet.metadata.damage or bullet.meta.damage? Essentially, metadata is a dictionary.

Isn't it literally just a dictionary? Couldn't it just be exposed as such? Instead of has_meta("abc"), set_meta("abc", true) it could just be "abc" in meta, meta["abc"] = true etc.

@YuriSizov
Copy link
Contributor

It is indeed just a Dictionary, but my guess would be that it is not exposed directly for safety concerns, to avoid unexpected mutations.

@AaronRecord
Copy link

It is indeed just a Dictionary, but my guess would be that it is not exposed directly for safety concerns, to avoid unexpected mutations.

Can you elaborate? I'm not sure I understand.

@Shadowblitz16
Copy link
Author

Shadowblitz16 commented Sep 15, 2021

Objects are basically typed dictionaries in dynamic languages.
lets take a look at what dynamically typed means...
https://stackoverflow.com/questions/1517582/what-is-the-difference-between-statically-typed-and-dynamically-typed-languages

A language is dynamically typed if the type is associated with run-time values, and not named variables/fields/etc. This means that you as a programmer can write a little quicker because you do not have to specify types every time (unless using a statically-typed language with type inference).

For one Godot is forcing people to attach script and declare variables on the object like it's statically typed which it's not.

Second there is no reason to keep a meta table at all. variables in dynamic types languages can be created and typed at runtime, That's what makes them so great!

Keeping it this way just adds for to more confusion.

why though that's what variables are for.
also its much more work to do something like that.

bullet.damage = 3 vs bullet.set_meta("damage", 3) is exactly the same amount of work. But the first one removes any expectations of a type, which everything in GDScript has, even if implicitly, and the second is safe.

Lets see here we have . and = which is two chars for my suggestion..
Your saying .set_meta( and " and " and , and ) is exactly the same amount of work? I don't think so.
It may not be that much work to type it out but it adds up, but that's not the main reason.
The main reason is you have to know meta exists unlike variables which in a dynamically typed language people assume you can create them on the fly. This means reading every corner of godot's documentation and godot's documentation is known to not be the best.

@YuriSizov
Copy link
Contributor

Can you elaborate? I'm not sure I understand.

I just assume that if it was exposed as a property, it would be possible to change it in a way that is out of the control of the engine. And that for whatever reason it may not be desirable to allow that. After all, meta is used by the engine itself.

@vnen
Copy link
Member

vnen commented Sep 15, 2021

#weapon code
func _on_timer_timeout():
  var bullet = bullet_scene.instance()
  bullet.damage = damage #bullet.damage may not exist.
#bullet code
func _area_entered(area):
  if damage: # check if damage exists
   body.health -= damage

I still don't get this example. If you have bullet code then it's a script attached to the bullet scene. If so, why can't you just declare a variable called damage here?

Allowing this pattern is essentially allowing variables to be used without declaration, which creates a multitude of opportunities to make mistakes without noticing. What if I have a code like this:

var is_damage_enabled = true
var damage = 0

func add_damage(amount):
    if is_damag_enabled:
        damage += amount

The user might be pretty confused when the add_damage() function never does anything and it'll take quite a while to notice that is_damag_enabled is misspelled. With current GDScript, it would give an error right in the editor as they're typing, which avoids this kind of mistakes.

Using . is the same thing: easy to misspell and don't notice it. In this case it will only trigger at runtime (assuming you're not using static typing) but it still gives an opportunity to catch and fix the mistake.

For one Godot is forcing people to attach script and declare variables on the object like it's statically typed which it's not.
[...]
The main reason is you have to know meta exists unlike variables which in a dynamically typed language people assume you can create them on the fly.

That's not really true. A lot of dynamically typed languages does not allow for monkey-patching objects at runtime like this. The ones that do allow it are usually criticized for it (such as Ruby and JavaScript).

This means reading every corner of godot's documentation and godot's documentation is known to not be the best.

If you want to use this feature you need to make sure it actually works like this, which requires reading the documentation anyway. Godot documentation has come a long way and those who think it's bad are probably just repeating what they heard 4 or 5 years ago.

If the issue is the (lack of) documentation about metadata, that can be improved.

@Shadowblitz16
Copy link
Author

Shadowblitz16 commented Sep 17, 2021

I still don't get this example. If you have bullet code then it's a script attached to the bullet scene. If so, why can't you just declare a variable called damage here?

Allowing this pattern is essentially allowing variables to be used without declaration, which creates a multitude of opportunities to make mistakes without noticing. What if I have a code like this:

it requires a script and it requires checking that the spawned and collide object is of type bullet.

Allowing this pattern is essentially allowing variables to be used without declaration, which creates a multitude of opportunities to make mistakes without noticing. What if I have a code like this:
This could be fixed with auto completion and a language scanner.

The user might be pretty confused when the add_damage() function never does anything and it'll take quite a while to notice that is_damag_enabled is misspelled. With current GDScript, it would give an error right in the editor as they're typing, which avoids this kind of mistakes.
Auto completion.

Using . is the same thing: easy to misspell and don't notice it. In this case it will only trigger at runtime (assuming you're not using static typing) but it still gives an opportunity to catch and fix the mistake.

Also how is . easy to misspell? if you mean the variable name after the . then again that's why the code editor should be improved.
The code editor already is ran at runtime. Why can't the variables, functions and signals be parsed into the auto complete popup?

That's not really true. A lot of dynamically typed languages does not allow for monkey-patching objects at runtime like this. The ones that do allow it are usually criticized for it (such as Ruby and JavaScript).

That's not really true. java script for one is hated for far more reasons then monkey patching.
Also there are benefits for monkey patching as well. Again as long as good auto complete support is provided for GDscript it won't be a issue.

If you want to use this feature you need to make sure it actually works like this, which requires reading the documentation anyway. Godot documentation has come a long way and those who think it's bad are probably just repeating what they heard 4 or 5 years ago.

We shouldn't have to read the whole documentation top to bottom for every corner case Godot has.
Godot should be designed to be simple, lightweight, easy to use and powerful.

There have been many cases something has been buggy incomplete, undocumented or implemented it a way that if far from simple or easy to use.

Again you are over looking the fact that..
#1 This could make the engine lighter since meta data and variables could be merged.
#2 There is no way to ensure a strong connection with packed scenes or signals that doesn't break when you move files around.
#3 This doesn't provide any down sides if a good language scanner is implemented for gdscript. Which should be encouraged.

Also no class_name doesn't work because it only uses the root of the scene and not the whole scene.
Also class_name requires a script, so that forces people to put a empty script on everything that people want to type check.

@dalexeev
Copy link
Member

Remember that using self to access member variables is optional:

var a = 1

func _ready():
    var b = 2
    print(a) # 1
    print(b) # 2

It's pretty handy. And if we allow the use of undeclared variables (especially if local ones too), then this will add some ambiguity, which will have to be solved in one of the ways:

  1. Make self required when referring to member variables (like this in JavaScript).
  2. Add an analogue of Python's global.
  3. Make the behavior dependent on whether the variable is declared/initialized or not (which is inconsistent).
func _init():
    self.a = 1

func _ready():
    b = 2
    print(self.a) # 1
    print(b) # 2

This is a more or less acceptable option. But any intermediate option will be unnecessarily confusing. And I generally dislike the idea of ​​using undeclared variables. It seems to me that clarity is much more important here than flexibility.

@Shadowblitz16
Copy link
Author

The point I am trying to make is it should be optional. (aka possible)
There are uses and benefits for monkey patching which I have already explained.
I don't have the time to sit here and argue my case for something that clearly make life easier on the developer if implemented correctly.

I am just telling you what I needed as a user of the engine.
If the Godot developer''s think they know better then someone who has been trying very hard to learn the engine for the past 2 years and simply wanted to make suggestions on how to make it easier to use then so be it.

But I still am still going to have to say I disagree

@vnen
Copy link
Member

vnen commented Sep 18, 2021

Language scanner (I assume you mean a static analyzer) would not solve the issue. It could at most show a warning in this case, because it would never be able to tell whether you intended to declare a variable (or check if a variable exists) or it was a mistake. But if you do rely on this feature, there'll be a lot of warnings and you either would disable this type of warning or you would ignore them. It would still be an issue, especially for people not expecting this to happen. Not to mention that a static analyzer will always be slow and will have to be run on demand, so it's very easy to get this issue without any warning.

Auto-completion don't solve it either because there are many cases where it's impossible to detect the type and thus list the possible member after the . (and yes, I did mean the name after the ., of course it's hard to misspell a single character...). Doesn't matter how much it is improved, it's literally impossible in some cases, so it won't be a solution. Even when it is, while it might prevent you from mistyping, if you did mistype then it won't help you to find where the mistake is.

I don't think I know better than anyone. Not a single person would know what the whole community wants. But this proposal don't seem to be getting approvals from the community. While I don't disagree this would be helpful for your project (although I still don't get the initial example, which is already using a script anyway), there are definitely other ways to go about it that doesn't require this to be implemented. And I don't really like the idea of "static support is bad, then let's make it worse", as I don't think that's the proper way to go. Just because we can't detect all errors, it doesn't mean we should detect less errors that we already can. We should make it better for everyone instead.

For the point of being optional, I'm not fond of it either. It just makes everything more complex: implementation, documentation, code samples. Very easy to get a tutorial and don't realize a particular option is enabled or disabled and because of that your code doesn't work. A beginner following the tutorial would have a hard time discovering what's wrong.

If the community do want something like this, then we can discuss a good design to lessen the pitfalls. But it doesn't seem to be the case given the interaction with this proposal.

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

6 participants