Replies: 23 comments 46 replies
-
I'm more of a static typer with defined structs and types, so I'm very rigid when it comes to structs, but since it's GDScript, I understand that people prefer more flexibility when it comes to how the data's represented. This is definitely about no other language other than GDScript, but if we were to be attaching bindings, I hope they can be represented as language-specific structs rather than array-like structs. That's just my take on this, though. |
Beta Was this translation helpful? Give feedback.
-
Another question that's just occurred to me is should subsets of names and/or values suffice for considering them equal?
What would
|
Beta Was this translation helpful? Give feedback.
-
I feel the same way. To be honest, I'm not able to come up with a single use case where considering them equal would be beneficial, it just struck me as an edge case and I wanted to see if others had use cases where it'd either benefit them or give them headaches. |
Beta Was this translation helpful? Give feedback.
-
I think that while the other proposal describes structs implemented in terms of dictionaries, they shouldn't expose their implementation to users. They should be convenient and clear in their own way instead of being dictionary-like because of their implementation. It should be reasonable to change the implementation of a performance -focused feature away from dictionaries in the future. 1: What should the syntax be for creating a struct instanceMy experience with Godot's type inference implies that the first indicates that the following two are valid: var named_int : NamedInt = [name = "Godot", value = 4]
var named_int := [name = "Godot", value = 4] as NamedInt
var named_int := [name = "Godot", value = 4] # surely not correct! So I'd prefer the However, given that classes are created with var named_int := NamedInt.new(name = "Godot", value = 4) 2. When should two structs be considered equalI think the answer for this depends on whether the implementation makes structs types behave like golang interfaces: allows OtherNamedInt to be passed to a function expecting NamedInt because they have the same data. (structural typing)
3. How should structs appear in documentationIf a function returns a struct with 10 fields or a struct containing other structs, only A ( |
Beta Was this translation helpful? Give feedback.
-
I personally think that A is way more clear in what it's doing, and I think that for documentation option a is also better. When it comes to equality, here's my two cents. struct Foo:
var a : int
var b : String
struct Bar:
var a : int
var b : String
var c : bool = false
var structa := Foo(a = 0, b = 1)
var structb := Bar(a = 0, b = 1, c = true)
# structa == structb should be false, as their absolute contents are different. Because it doesn't make sense for two structs with different sets of variables to be equal (even if their contents are the same), I propose a method which checks all the variables with shared names for equality. Something like # returns true, because `a` and `b` have the same values in both.
structa.named_parameters_equal(structb) Obviously this needs more clarification on how it should work (some people may want floats with no decimal point and ints to be treated as equal for reasons), but I think that a few functions for checking equality of structs with names/positional values/etc, would be the best solution for this. |
Beta Was this translation helpful? Give feedback.
-
Question 1 → a) Question 2 → same reference; the rest of options (same type structs, same name, same value, and same name and value and struct type, etc etc) should be evaluated with operators and auxiliary methods. Question 3 → definitely not [] and {} for its members to avoid confusion with arrs and dicts.
|
Beta Was this translation helpful? Give feedback.
-
I'd rather prefer something more dictionary-based. I'm assumed they will be otherwise used very much the same as dictionaries even if the implementation is very different. Putting square braces, or the like is more likely to confuse users as to how structs are used--even if the implementation is more array like, that's not a detail the user needs to care about. Let me just spitball something like: struct my_struct {name = "Godot", value = 4} With static typing I think that, as with "enum," a struct should be kind of its own thing, not introduced by var. I'm not certain what I think about equality. My usual inclination would be to say that my_struct is a type, and only objects of the same type are equal, but for duck-typed languages that might not be so useful. I guess the question becomes, for what purpose has the user created objects with the same signature but different aliases? |
Beta Was this translation helpful? Give feedback.
-
I like the idea of duct-typing (if two structs have the same fields, allow them to freely being assigned to each other). What do you think about being able to upcast structs? The ability to assign a value of
|
Beta Was this translation helpful? Give feedback.
-
If we arranged the struct fields in memory alphabetically, then in this example:
If we were to consider |
Beta Was this translation helpful? Give feedback.
-
For the benefit of folks that mostly interact with these discussions via email notifications, I'm copying a fairly lengthy update I've made to my OP.
These are related to my question 2 above, but go a step further by looking at cases where one struct has the same fields as another but in a different order or one struct has more fields than other. For example, let's consider a few different structs. struct NamedInt:
var name : String
var value : int
struct Named:
var name : String
struct NamedValue:
var name : String
var value : Variant
Struct IntNamed:
var value : int
var name : String
var named_int := NamedInt(name = "named_int", value = 3)
var named := Named(name = "named")
var named_value := NamedValue(name = "named_value", value = "not an int")
var int_named := IntNamed(value = 5, name = "int_named") With these in mind, we can think about which of the following structs should and should not support. The lines in the following code block are meant to be read in their own context, not as sequential instructions. # upcasting -----
# implicit upcast
named = named_int
# explicit upcast
named = named_int as Named
# explicit up-construction
named = Named(named_int)
# down casting -----
# implicit downcast
named_int = named
# explicit down cast
named_int = named as NamedInt
# explicit down construction
named_int = NamedInt(named)
# permutation casting -----
# implicit permutation cast
named_int = int_named
# explicit permutation cast
named_int = int_named as NamedInt
# explicit permutation construction
named_int = NamedInt(int_named)
# recursive upcasting -----
# implicit recursive upcast
named_value = named_int
# explicit recursive upcast
named_value = named_int as NamedValue
# explicit recursive construction
named_value = NamedValue(named_int)
# recursive down casting -----
# I think you get the picture by now
# recursive permutation casting -----
# I think you get the picture by now In each of these situations, I could imagine a few different levels of support
Taken together, these amount to A LOT of questions to be answered, but I think they are pretty important design decisions that should not be made lightly. This is my personal stance at the moment, but it could easily be swayed by a good argument.
My reasoning at the moment is loosely based on how subtype polymorphism is defined in the textbook Types and Programming Languages. What do you all think? |
Beta Was this translation helpful? Give feedback.
-
I haven’t followed this discussion too closely but from the update examples you just posted I agree with all points of your stance, especially with downcasting throwing errors unless it uses the constructor |
Beta Was this translation helpful? Give feedback.
-
It looks to me like a number of these questions boil down to whether the GDScript structs should be nominally or structurally typed. For my part, I prefer nominal typing, and I essentially agree with @Stwend's claim that
Since I think that the answers to most of the questions posed in this thread become fairly straightforward if we accept the premise that a struct's type should be considered part of its identity, I'd like to offer a more robust defense of this position. I'm skeptical of the sort of structural typing ideas being floated here for a couple reasons.
The
|
Beta Was this translation helpful? Give feedback.
-
From the issue: #7329 (comment)
|
Beta Was this translation helpful? Give feedback.
-
Can't you just make a new class? |
Beta Was this translation helpful? Give feedback.
-
so there doesnt exist any room to a future possible lightweight data structure that lives in the memory stack? (due to Variant) |
Beta Was this translation helpful? Give feedback.
-
Hi all, it's been a while since I've updated this discussion, but as my work on adding Structs to GDScript is nearly ready to review, I have encountered another essential question. I've updated my original post with this question as well for posterity. Question 5. When (if ever) should casting between Structs and Arrays or Dictionaries be automatic?Consider the following example. struct NamedInt:
var name : String
var value : int
var named_int := NamedInt(name = "Godot", value = 4)
named_int = ["Godot", 5] # should Array to Struct be automatic?
named_int = {named = "Godot", value = 4} # should Dictionary to Struct be automatic?
var array := []
array = NamedInt(name = "Godot", value = 4) # should Struct to Array be automatic?
var dictionary := {}
dictionary = NamedInt(name = "Godot, value = 4) # should Struct to Dictionary be automatic? Each of the cases above will certainly work if the user adds the appropriate explicit cast using I'm interested to hear what you all think. |
Beta Was this translation helpful? Give feedback.
-
Maybe make implicit casting trigger a breakpoint, but not crash. Or at
least print an error in debug builds.
If something slips through in a production release, it would be nice to
avoid crashing the player's game.
…On Mon, Oct 7, 2024, 7:09 PM nlupugla ***@***.***> wrote:
I like the idea of having an "unsafe cast" warning.
The reason I'm inclined against implicit casts is that I am imagining that
if a user has gone through the effort of creating a struct, rather than a
simple Array or Dictionary, that user probably cares about static typing
enough that they wouldn't want their types to be sneakily stripped away.
That said, I could easily imagine users that see things differently, so I'm
happy to be persuaded in the other direction.
—
Reply to this email directly, view it on GitHub
<#7903 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACHIV2XDKGUEMI6EH54HWDZ2MIB7AVCNFSM6AAAAAA5LGJJ46VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTAOBXGM3TCMA>
.
You are receiving this because you were mentioned.Message ID:
<godotengine/godot-proposals/repo-discussions/7903/comments/10873710@
github.com>
|
Beta Was this translation helpful? Give feedback.
-
I learned today that the ability to pass arguments by name in general is untenable due to some GDScript limitations #902. An argument could be made that an exception could be made for Struct constructors, but I think it's probably best to keep things consistent with the rest of the language. So if constructing via Note you could always use a Dictionary if you wanted to assign values by name via |
Beta Was this translation helpful? Give feedback.
-
@nlupugla I would like to see the code for this implementation. You said its ready for review? |
Beta Was this translation helpful? Give feedback.
-
I am working on a newer implementations of structs (PR) so I wanted to check in here and see if I understand the outcomes of the discussion, and also check if the conclusions are still valid, and that I understand them. To help focus discussion, I want to split this into 2 different comments. The first is regarding syntax, and the second is regarding behaviour. I also have my own thoughts on each, and I will post those in replies. Basic Struct Type Declaration# Basic struct type declaration
struct NamedIntExtra:
var name : String
var value : int
var extra : int I don't think there are any disagreements about the above syntax Only thing I want to note is that the specific implementation I am working on benefits strongly from statically typed (non-Variant properties). As a result, I would want to encourage people to not use Variant in their projects, or at least discourage it by forcing them to statically type to Variant if/when that was their intention. PrintingNot a whole lot has been said here. I think this isn't super important to people. Best case appears to have been made for DocumentationOptions presented by OP were: I think the only real votes were for A. But I imagine this is similar to printing where it isn't a significant issue, so long as the information is available and easy to access and parse. ConstructionThere seems to be a lot of disagreement about the specific syntax when it comes to construction of structs. var named_int := NamedInt(name = "Godot", value = 4) # confusing, hard to distinguish from a function call, not feasible without big changes
var named_int := NamedInt("Godot", 4) # even more confusing, creates problems if names changed order
var named_int := NamedInt<name = "Godot", value = 4> # doesn't look good, doesn't follow any standards or expectations
var named_int := NamedInt{name = "Godot", value = 4} # potentially confused with dict, but otherwise clear, and allows for easy substitution into existing projects
var named_int:NamedInt = {name = "Godot", value = 4} # however, what would be the *expected* and *actual* behaviour here?
var named_int:NamedInt = function_that_returns_a_dictionary() # or here?
var named_int: NamedInt = [name = "Godot", value = 4] # definitely confusing, and nothing to distinguish it from valid array syntax so incompatible with ducktyping
var named_int: NamedInt = ["Godot", 4] # same problem, but potentially worse?
var some_var: Variant = function_that_returns_a_struct() # what would happen if I wrote some_var = ["Godot", 4] on the next line? So it seems to me that any I think most can agree that Much has been said on It does seem to me that construction is where most of the disagreements lie in regards to structs, and there is no consensus or agreements here. |
Beta Was this translation helpful? Give feedback.
-
What follows is about the behaviour of structs. Reference-type vs Value-typeI haven't seen much discussion on this here, but it has been somehat in other locations. It seems to me that most people here presume structs are reference-typed? I do think that this question is important, as it impacts and relates to the aspects described below, so I would like anybody who responds to any of the below questions to have considered this in their response. (To be clear on definition here, a reference-type would behave like an Array or a Dictionary, and value-type would behave like a String) Equality ComparisonIf we presume that structs are reference-type, then the result of an If we presume that structs are value-type, then the result of an I think the above are safe assumptions, and nobody has argued against either. So the questions that therefore remain are:
I don't think anyone has made a compelling case for "yes" on that last one. Opinions are very mixed on the first. It seems to me that the answer depends on how much structs are viewed as "extra lightweight objects", or as "sets of values". So might be we need to think on that and decide on a philosphy, and an answer on whether they are ref-type or val-type? The only compelling reason I have seen that should allow for a "yes" for any question after the first is to allow for comparisons, and to allow for something like polymorphism between structs (without needing inheritance). Therefore, these are only questions if we presume that either: structs are ref-type and castable between types with polymorphism like objects, OR structs are val-type and cannot be cast to another struct-type. Which again comes down to a question of philosophy, but now also of cast behaviour. Casting Between Non-Struct TypesThe consensus here appears to be that this shouldn't be possible. Neither implicitly, nor explicitly. Where desirable to be allowed, it should be performed by calls to functions that explicitly create new values (e.g. Casting Between Struct TypesA case has been made to allow for specific casts under specific circumstances, with varying degrees of error behaviour. Seems there has been little disagreement here, and I suspect that is related to the complexity of this, and the pending question of whether structs should be ref-type or val-type. |
Beta Was this translation helpful? Give feedback.
-
A few notes. I'd pefer if structs were not so verbose. Could we declare each property without having write "var": Likewise, could the type just be used as: As for Reference-Type Versus Value-Type - I'd say Reference-Type. The fact that you can't pass-value types by reference, ever, like in C# say, is a real impediment for simple types in many common situations. Sometimes, you end up wrapping the thing in an Array just to use it that way, which is an awful hack. If it was a value type that could be passed by reference, that'd be ideal, but that's never going to happen. |
Beta Was this translation helpful? Give feedback.
-
Structs vs RecordsIt seems to me that much of the community has need for both ref-type and val-type structs. And that there are signficicant problems with both approaches as a result. For example, structs are definitionally val-type objects in most languages, and so that is behaviour that people are likely to expect. However, if structs did not exhibit ref-type behaviour, then people would be forced to use hacky solutions to enable or facilitate it. So, I had an idea... What if we introduced both using distinct keywords?
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi Godot community! I have been having a lot of fun implementing the proposal by @reduz #7329 for adding struct-like types to GDScript. That proposal was mainly concerned with how to implement structs as Arrays with individually named and typed elements. It briefly discussed the syntax for using them in GDScript and C++, but did not go in to details on how this new feature should behave.
I'd like to open a channel for discussing the nitty gritty details of structs as a language feature in GDScript. To be clear, I don't want to discuss how they will be implemented, that is better discussed in the original proposal. I've listed below a few of the questions that I'm currently puzzling over.
For the sake of concreteness, let's consider a struct defined in GDScript like so:
Question 1: What should the syntax be for creating a struct instance?
Some possibilities:
a)
b)
Question 2. When should two structs be considered equal?
Question 3. How should structs appear in documentation?
Suppose a function returns a
NamedInt
. Here are some ways that fact could be displayed in documentation.a) NamedInt
b) Struct[String, int]
c) Array{String, int}
d) [name : String, value : int]
More to come...
There are many more questions to be answered about the new struct-like type. I'll try to update the post with particularly hairy ones that are brought up in the discussion.
Looking forward to hearing everyone's ideas!
Edit December 13 2023, 9:54:
Thanks for the discussion so far everyone! There seems to be a consensus that when it comes to creating a struct, the
syntax is preferred over something like
Another question has come up as I've been working on structs.
Question 4. How should structs be formatted when printed?
For example, what should the following output?
Here are some of the options I'm considering.
a) [name: "Godot", value: 4]
b) {name : String = "Godot", value : int = 4}
c) NamedInt(name = "Godot", value = 4)
What do you think?
Edit January 11 2024, 10:46:
@AndrewAPrice has brought up some interesting questions about duck typing and when two structs should be considered equivalent.
These are related to my question 2 above, but go a step further by looking at cases where one struct has the same fields as another but in a different order or one struct has more fields than other.
For example, let's consider a few different structs.
With these in mind, we can think about which of the following structs should and should not support. The lines in the following code block are meant to be read in their own context, not as sequential instructions.
In each of these situations, I could imagine a few different levels of support
Taken together, these amount to A LOT of questions to be answered, but I think they are pretty important design decisions that should not be made lightly. This is my personal stance at the moment, but it could easily be swayed by a good argument.
My reasoning at the moment is loosely based on how subtype polymorphism is defined in the textbook Types and Programming Languages.
What do you all think?
Edit October 6 2024, 13:44:
Hi all, it's been a while since I've updated this discussion, but as my work on adding Structs to GDScript is nearly ready to review, I have encountered another essential question.
Question 5. When (if ever) should casting between Structs and Arrays or Dictionaries be automatic?
Consider the following example.
Each of the cases above will certainly work if the user adds the appropriate explicit cast using
as
, but the question is if the casts should occur implicitly as well. I am inclined to say that these kinds of casts should never be implicit, but I can also see the appeal of being able to write the very concisenamed_int = ["Godot", 5]
over the more verbosenamed_int = NamedInt(name = "Godot", value = 5)
ornamed_int = ["Godot", 5] as NamedInt
.I'm interested to hear what you all think.
Beta Was this translation helpful? Give feedback.
All reactions