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

GDScript proposal: 'with' statement #1736

Closed
GalanCM opened this issue Apr 25, 2015 · 9 comments
Closed

GDScript proposal: 'with' statement #1736

GalanCM opened this issue Apr 25, 2015 · 9 comments

Comments

@GalanCM
Copy link

GalanCM commented Apr 25, 2015

I have a idea I'd like to play around with coding, but I'd like to get people's thoughts before delving into it.

In Godot, it's common to get nodes as such:

var node  = get_node("node")

Which is fine, until you try using this node

node.get_name()

but the node doesn't exist:

Attempt to call function 'get_name' in base 'null instance' on a null instance.

This is a common occurrence when nodes are deleted, and this strong binding between nodes can be problematic. If I have a commonly used node (such as the player sprite) and I decide later that I want it to disappear (when dying for example), I now need to scour my code for instances of it to make sure my game won't crash.

Right now, this means wrapping each instance of get_node() (or similar) with:

var node = get_node("node")
if node != null:
  # do something with node

What I am proposing is a simplified syntax:

with get_node("node") as node:
  # do something with node

with would run the statement (get_node in this case), then if the result isn't null, assign it to the variable after as, and run the code block.

On the surface, this is just a small bit of syntactic sugar, but it also opens up some possibilities.

The first is automatic debugging facilities. While you may generally want to be able to gracefully delete a node using this pattern, it could also lead to unexpected behavior if overused. As such, a warning could be automatically printed to the console in the case of a null result:

[Notice]: Null result in 'with' statement in [NodePath] line [#], col [#]. This may result in unexpected behavior.

Of course, this might actually be expected behavior, in which case it would be nice to handle it intentionally.

with get_node("node") as node:
  # do something
without: # or perhaps 'else'
  # do something else

In a sense this would create a case-specific try/except block.

Of course, with is a common keyword in other languages, and its usage varies between them, so perhaps another keyword might be preferred (sadly, using has the same problem). Or perhaps we could expand it to share behavior with the language that (sorta) gave me this idea, and GDScripts main influence, Python, by having it use optionally support construction and deconstruction callbacks (_with_enter/_with_exit?) if the statement results in an object. Of course, Python's primary use for those, file handling, may be unnecessary in GDScript.

@GalanCM GalanCM changed the title GDScipt proposal: 'with' statement GDScript proposal: 'with' statement Apr 25, 2015
@bojidar-bg
Copy link
Contributor

+1, though you can use if statements instead.... 😄

@fazjaxton
Copy link

+1. While the "if" check is almost equivalent from a functionality point-of-view, I find the "with" syntax to be much easier to visually parse what is going on. It is also nice that it requires one line instead of two.

@volzhs
Copy link
Contributor

volzhs commented Apr 26, 2015

but what if someone cache the reference of node like this.

var node = get_node("node")
with node as Node:
    # do something
without:
    # do something else

I think it's the same thing.

@GalanCM
Copy link
Author

GalanCM commented Apr 27, 2015

The main advantage of this over the if statement approach is in the aforementioned debugging and refactoring approach, with becomes a bit more complicated to replicate using if statements:

var node = get_node("node")
if node != null:
  # do something
else:
  print("[Notice]: Unexpected null value")
  print_stack()

Not the worst thing in the world, but coming from a Python background, I'm a fan of using syntactic sugar to support, and more importantly promote good design patterns. Python's with statement is an excellent example of this: it doesn't force anyone to use the pattern, and the pattern can be replicated without the syntax, but by being there, it makes it more clear that this is the best way to do things. Whether that's the case here, I'm not 100% sure, but I feel it might be.

but what if someone cache the reference of node like this.

var node = get_node("node")
with node as Node:
    # do something
without:
    # do something else

I think it's the same thing.

You'd be correct. Since with would evaluate the "node" expression it would be functionally the same. While this use case might be a bit silly, having it evaluate an expression gives it added flexibility. For example, I like using groups to handle nodes that might not stay in the same place in the SceneTree, so I would find myself using

with get_tree().get_nodes_in_group("Player")[0] as player:

quite often.

@ficoos
Copy link
Contributor

ficoos commented May 18, 2015

with in python is completely different from what you are describing here. The with statement scopes a resources.

It replaces:

lock.acquire()
try:
   ....
finally:
   l.release()

or:

f = open("path")
try:
   ...
finally:
   f.close()

Using with shows the following intent: You want to scope the use of a resource. Said resource implements the context interface and that way you have a uniform way of objects declaring that they need\can be scoped and for you to read that the intent is scoping.

The traditional way of using try/finally obscured the the original intent as the reader would not know if the purpose of the score was catching error or taking care of scope resources.

What you are suggesting here is completely different. It's just pure syntactic sugar that serves a very narrow purpose. I would even argue that it's bad pad practice to expect nulls in your nodes except for very special cases.

As for

with get_tree().get_nodes_in_group("Player")[0] as player:

Godot makes things like that hard on purpose as it's an anti-pattern to use that everywhere in your code. You should actually put that in a method on fixed parent in the tree (e.g game, level). That gives the subnode context and makes sure that you can only do get the things you need (like the player) from a logical context like a level where you will most likely find it and not in the options menu where it will probably be null.

## Level.gd
var _player = null
func _ready():
    _player = get_tree().get_nodes_in_group("Player")[0] # Or get it from the initial known position

func get_player():
   return _player # No runtime lookup, works no matter where the player is moved.

## SomeNode.gd
var _level = null
func _ready():
   _level = get_tree().get_node("/root/game/level")

func _do_something():
   var player = _level.get_player()

Saving one line of code for something that is probably an anti-pattern anyway is not something I'd like to see in gdscript.

@amedlock
Copy link

amedlock commented Sep 4, 2017

I would argue for "else:" rather than "without:".

@akien-mga
Copy link
Member

Based on the description above (and the one in #18345 for another use case), I don't think we'll implement any with statement in GDScript, as it is used in different languages for different things (simple using like in VB, scoping like in Python, context change like in GMS), and none of those seem really relevant for GDScript's architecture.

The proposal in this issue is for a kind of sugar which is hardly relevant now that you can use

$node.get_name()

instead of

var node = get_node("node")
node.get_name()

If you need with for a long block of code, it's a hint that this code should likely be placed in the script of that node, and replaced by a single function call in the parent node.

@Calinou
Copy link
Member

Calinou commented Mar 24, 2020

@Xrayez get_node_or_null() is only about not printing an error message if the node can't be found. It doesn't impact the final behavior in any way. In both cases, the method will return null if the node can't be found.

That said, if node: is basically equivalent to if node != null: if you use static typing to ensure the variable's type doesn't change over time.

@Xrayez
Copy link
Contributor

Xrayez commented Mar 24, 2020

@Calinou thanks, I deleted the message entirely, it was written by me being sleepy at 3 am. I guess I somehow misinterpreted the usage as of:

if has_node("node"):
    var node = get_node("node")

where get_node_or_null() would fit just fine.

This issue was closed.
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

10 participants