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 with(instance) with constructor syntax in GDScript #623

Open
lukostello opened this issue Mar 23, 2020 · 11 comments
Open

Add with(instance) with constructor syntax in GDScript #623

lukostello opened this issue Mar 23, 2020 · 11 comments

Comments

@lukostello
Copy link

lukostello commented Mar 23, 2020

Describe the project you are working on:
I am working on a puzzle game which works similar to Rubik's cubes and combination locks

Describe the problem or limitation you are having in your project:
One of my objects is changing the effect of another in a lot of ways and it is tedious to keep doing otherobject.property1.dosomething()
otherobject.property1.dosomethingelse()..etc

Describe the feature / enhancement and how it helps to overcome the problem or limitation:
if we had a with(instance) method like game maker then it would be less tedious an easier to read.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
https://docs.yoyogames.com/source/dadiospice/002_reference/001_gml%20language%20overview/401_18_with.html

If this enhancement will not be used often, can it be worked around with a few lines of script?:
Either tediously or by writing a method in another class when organizationally it might not fit as well there

Is there a reason why this should be core and not an add-on in the asset library?:
Its used fairly often in gml I don't see why it wouldn't be in here.

@lukostello lukostello changed the title with(instance){ with(instance) with method Mar 23, 2020
@lukostello lukostello changed the title with(instance) with method with(instance) with constructor Mar 23, 2020
@bojidar-bg
Copy link

This was discussed previously in godotengine/godot#18345 and godotengine/godot#6300 (and godotengine/godot#1736).

So far consensus has been that such a feature would not be necessary in GDScript.

Consider that it is mostly sugar for a variable assignment:

with some.thing:
    property = 4
# - can also be written as ->
var x = some.thing
x.property = 4

And it will behave the same.

And at times, it can be confusing to someone unfamiliar with the semantics:

with $node_a:
    $node_b.position = position
# Does it mean this:
get_node("node_b").position = get_node("node_a").position
# Or does it mean this?
get_node("node_b").get_node("node_a").position = get_node("node_a").position
# Or even one of those?
get_node("node_b").position = position
get_node("node_b").get_node("node_a").position = position
var a = 5
with b:
    c = a
# Does it mean this:
b.c = 5
# Or does it mean this?
b.c = b.a

# Likewise
with x:
    y = z
# Does it mean this:
x.y = z # which is an error if z is undefined
# Or does it mean this?
x.y = x.z

In addition it might result in some nontrivial code changes to autocompletion (since it has to be told how with statements should be autocompleted)


This is a proposal, however, so feel free to discuss it, if you consider that the feature can be beneficial in an object-oriented language such as GDScript.

@lukostello
Copy link
Author

lukostello commented Mar 23, 2020

The way I envision it (and remember it in GML) is that it is effectively a writing a method in the class of the instance being "withed" But it doesn't always make sense to organize that method in that class. Sometimes it makes more sense to organize it somewhere else.

Take for example a ball. Suppose this ball is used for many sports. And in each sport the ball behaves differently depending on how it is used in each sport. So rather than writing a function

use(sport)
{
   match sport:
   baseball:
       hitIt()
   bowling:
       rollIt()
   football:
       ThrowIt()
}

you'd write in the baseball class

with(ball){
    HitIt()
}

Note that the methods would actually be written there I just wrote as if they are being called there as a shorthand. Its not a perfect example. But I think its enough to illustrate what I'm imagining. Its an effective way to put what would normally be methods of another class in a different class for organizational purposes. Mostly I don't like having to write otherObject.property over and over. But I also want to keep by code organized in a way that isn't cluttered and weird.

@Xrayez
Copy link
Contributor

Xrayez commented Mar 23, 2020

This was discussed previously in godotengine/godot#18345 and godotengine/godot#6300 (and godotengine/godot#1736).
So far consensus has been that such a feature would not be necessary in GDScript.

At least that's a demonstrable number of feature requests I'd say, including this one.

See discussion in godotengine/godot#25669. In there people suggest adding with keyword for variable scope/visibility. What if the same keyword could implement the functionality behind both this proposal and act as a scope statement?

By the way, the with keyword as described here seems to be used in Pascal too. In theory this could also help with dictionary operations:

collision = {}
collision["point"] = result.point
collision["normal"] = result.normal
collision["rid"] = result.rid
collision["collider_id"] = result.collider_id
collision["shape"] = result.shape
collision["linear_velocity"] = result.linear_velocity
collision["metadata"] = result.metadata

vs:

collision = {}
with collision:
    point = result.point
    normal = result.normal
    rid = result.rid
    collider_id = result.collider_id
    shape = result.shape
    linear_velocity = result.linear_velocity
    metadata = result.metadata

Alternatively, scope keyword could be used for that. With no arguments passed to scope, it would act as you expect, well, creating a scope. If it takes more than one argument, then it would work just like the suggested with: "looking up" fields/properties inside an instance/variable (for assignment at least), also creating a new scope:

var a = Vector2(0, 0)
scope:
    var a = Vector2(1, 1)
print(a) # prints Vector2(0, 0)
var a = Vector2(0, 0)
scope a:
    x = 1
    y = 1
print(a) # prints Vector2(1, 1)

@katoneko
Copy link

Dart has so called cascade notation, which does the thing being described (acting on an object in sequence).

var button := Button.new()
    ..icon = Texture.new(...)
    ..text = "Press me."
    ..flat = true

add_child(button)

@Calinou
Copy link
Member

Calinou commented Mar 24, 2020

@katoneko I'm afraid that particular syntax doesn't save any meaningful amount of space (horizontal or vertical). However, it makes refactoring harder as you may have to move the initial var declaration when moving variables around.

In general, I think we should be optimizing for easier refactoring rather than aiming to type the lowest amount of characters possible. Many other modern languages seem to be going in that direction. Also, explicit is better than implicit 🙂

@Xrayez
Copy link
Contributor

Xrayez commented Mar 25, 2020

Also, explicit is better than implicit

If everything is explicit, nothing becomes explicit, so we get unnecessary verbosity over explicitness at the end of the day, which doesn't allow the developer to focus on things that really matter: algorithms.

Also, making a language explicit makes more sense for languages with static typing rather than dynamic, which is implicit. I'd like GDScript to remain its implicitness to some extent, but with the advent of static typing in GDScript people tend to choose being "explicit" by annotating each and every variable/method almost religiously, without considering the practical aspect of doing so.

Following the principles which are never questioned is the worst thing a person can do in terms of individuality and the problem at hand. Mentioning general development principles just signifies that there's no certainty over the feature being useful or not.

But thinking in terms of collective, mass development which is so inherent in open-source software development, I'd say that yeah explicit may be better than implicit and may be the actual reason for those languages adopting such principles, because that seems to be the bulletproof way of ensuring readable, self-documenting code which can connect developers on the mental map level, and because a lot of people don't seem to document the features in the first place due to laziness/lack of resources/time/motivation, you name it. It all boils down to the human factor as I say.

Краткость — сестра таланта (Brevity is a sister of talent) - Anton Chekhov.

Bold IMHO. 🙂

@Shadowblitz16
Copy link

in gml it can be used with a instance or a type

for example if you do..

with(get_node("Sprite")):
   h_frames = 1;

it only works on that instance. but it you do..

with(Sprite):
   h_frames = 1;

it does it to all Sprites instances that exist in the game

@bojidar-bg
Copy link

Working with all instances of a type sounds dangerous to me, so I think it is better to make it work for arrays, and ask users to use groups.

In this case it would become something like this for loop:

for x in get_tree().get_nodes_in_group("Sprite"):
    x.h_frames = 1

But I find a bit less reason to make with work with both arrays and actual objects. Orthogonal features are usually better, as they allow things to be implemented only once (less surface for bugs) and helps users decide which one to use (less time wasted weighting alternatives).
In this case, with(Sprite): h_frames = 1 might be written as:

for sprite in get_tree().get_nodes_in_group("Sprite"):
    with sprite:
        h_frames = 1

# Alternatively, with less indent:
for sprite in get_tree().get_nodes_in_group("Sprite"): with sprite:
    h_frames = 1

@Shadowblitz16
Copy link

Shadowblitz16 commented Mar 30, 2020

I think that allowing the user to add logic to every instance of a type could be slow but it might also have be good for sorting nodes.

maybe there could be a optional argument to the with statement like so..

with Node2D, position.x > 0 && position.x < 24:
  #all node2d's with a x position between 0 and 24

@SilencedPerson
Copy link

I would just like to add that this feature would streamline building finite state machines in Godot and potentially be substitute/replacement for nodes having multiple scripts.

user could add Node with a script, then wrap it's logic in with statements like this:
Screenshot from 2021-11-17 12-24-58

@Shadowblitz16
Copy link

This would be really useful for component oriented design.
We could have a whole node dedicated for running logic in a with statement on a specific node.
And if we support exporting properties from inside a with statement, it would allow for containerizing logic in scenes.

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

7 participants