YoYoGames / GameMaker-Bugs

Public tracking for GameMaker bugs
26 stars 8 forks source link

In Game: Make "with" statement work with arrays #4274

Open iampremo opened 1 year ago

iampremo commented 1 year ago

This must've come up in the discord/GMC as it's appeared verbatim in 3 tickets this morning: Ticket #208429 Ticket #208431 Ticket #208432

Feature Type: feat_change_ide Description: Right now, "with" statement can apply to structs, instances, object types and special keyworks (all, noone, etc.). However, sometimes one might need to perform "with" operations on multiple object types or other entities. One example would be applying "with" statement to multiple object types with a certain tag, which avoids the limitation of one-parent-per-object inheritance.

Thus, I suggest having "with" statement work on arrays, which would be functionally the same as iterating through the array and running "with" statement on each array item. So a new statement along the lines of: with (my_array) { // do the thing }

would be functionally the same as: for (var i = 0; i < array_length(my_array); i++) { with (my_array[i]) { // do the thing } } Benefit: It would greatly simplify handling of the multi-object or multi-struct with statements. Example applications include:

A built-in array processing might end up faster than a hand-written loop (especially on VM exports), but the primary motivation is to simplify processing arrays of structs/instances/object types. Link:

DragoniteSpam commented 1 year ago

There's a running joke in Juju's discord about how you guys must instantly know when we jump on a feature request because you get like ten of the same ticket overnight

Gamer-XP commented 1 year ago

I think we should make it "foreach" instead of "with" though. +, if possible, add a support to custom struct-based collections and data structures.

DragoniteSpam commented 1 year ago

My argument against making it "foreach" is that we already have "with" and multiple "foreach" functions, and at some point it's going to get confusing explaining to people which they should use for what.

This functionality is most similar to "with," so it should keep that name.

rwkay commented 1 year ago

Adding my comment from the internal JIRA ticket for this feature....

After discussion with the Cronus team there is a danger in just applying this blindly to the current syntax as that means that legacy code will unexpectedly work differently (and no longer error in situations that it currently would)...

So the current proposal is to add a new keyword (each) this would change the with behaviour and mean that the new semantics are in effect (and signal that the programmer understands this) so

with( my_array ) {
}

would error, but

with each (my_array) {
}

would work as above.

AtlaStar commented 1 year ago

Counterpoint; with shouldn't work over arrays as a form of syntactic sugar, but should work with tags instead, as the issue itself derives from weaknesses in the inheritance system which tags were designed to circumvent.

Oracizan commented 1 year ago

I feel that with is already an overloaded keyword (converts an object reference to a list of instances to loop over + drops you into the instance scope) and I would prefer not to see more functionality added to it.

I propose that, instead of adding a new, narrowly focused each keyword, the same behavior would be better accomplished by adding broader purpose array looping and scope-control features.

Something akin to the common for (x in my_array) syntax would:

This would all provide clarity not just to the programmer but also (I believe) the compiler.

A small convenience function of instance_find_all(obj) that returns an array of all existing instances of an object would make this proposal equivalent to with while also covering the functionality requested in the original ticket.

attic-stuff commented 1 year ago

So the current proposal is to add a new keyword (each) this would change the with behaviour and mean that the new semantics are in effect (and signal that the programmer understands this) so

i like this however my concern would be potentially losing each as a cool keyword to use for other stuff.

gnysek commented 1 year ago

If with cannot work with arrays, then adding with each have no sense, as it suggests those both keywords are working in similar way, which isn't true in that case.

Also, in long-term, such keyword should also allow to iterate over ds_list and ds_map IMHO, so foreach or iterate sounds better (IMO), and doesn't lead to confusion.

with each also means reserving each as keyword (so it cannot be as variables anymore), while foreach isn't that common word.

AtlaStar commented 1 year ago

If with cannot work with arrays, then adding with each have no sense, as it suggests those both keywords are working in similar way, which isn't true in that case.

Also, in long-term, such keyword should also allow to iterate over ds_list and ds_map IMHO, so foreach or iterate sounds better (IMO), and doesn't lead to confusion.

with each also means reserving each as keyword (so it cannot be as variables anymore), while foreach isn't that common word.

Working over the other structures requires them to first make those data structures references rather than ID valued things...then there is the fact working over those data structures (arrays as well) also requires checking the value type of each element, as iterating over each and having a double, string, int64, etc stored in the array is nonsensical in such a case, as there is no self context or variables to access. So now with also has to check to see whether it is working over a value or a ref per element.

Extending with to work over any of the data structures honestly makes little sense. Honestly every issue that could be solved by extending it to work over data structures would work if they extended tag functionality and allowed with to iterate over tags by making them a proper type.

You'd be able to mark objects by tag and iterate over those instances. You'd be able to do the UI stuff mentioned in the original post if tags had an inheritance chain that with respected. You'd be able to perform it over all collisions by having a special tag that marked things involved in the current collision, with the engine removing those tags at the beginning of a collision function call. Best of all it'd be a much easier interface to use for new and old users alike.

Alphish commented 1 year ago

Just for the record, the with (array) suggestion originally came from a discussion about tags. Something that would allow easier iterating over an array of tags, while at the same time not requiring a whole system of tag assets. Don't get me wrong, better integration of tags into the codebase is also something I'd like to see implemented, but also I feel it'd require much more design and implementation work to pull off. Also, with (array) feels very intuitive to me, like "with all elements in this array" seems like a natural extension of "with this instance" or "with all instances of this object".

At the same time, tags don't fully replace the use-cases for with-each. The remark about: "You'd be able to do the UI stuff mentioned in the original post if tags had an inheritance chain that with respected." That one's incorrect. The UI stuff is about iterating through container UI children, which are different from inheritance children. E.g., a ui_StackPanel container could have instances of ui_TextBox, ui_Button and ui_Button as its children, while another ui_StackPanel would have three instances of ui_Button instead, and I'd want to iterate over the contained components of the first ui_StackPanel without touching the buttons in the other ui_StackPanel. Tags system won't suffice there. Same for colliding instances obtained via instance_place_array or similar function (if these were made) - the whole point is to get a subset of specific instances.

Neither with-each replaces the better tags integration, nor the better tags integration replaces with-each; I'd like to get both, eventually. Same with for-each construct - getting easier semantics for iterating through arrays would be definitely great, but I'd also like the syntactic simplicity of witheach (some_array) {...} compared to foreach (item in some_array) { with (item) { ... } }. Especially since the former is easier to teach to newcomers - it only requires introducing the concept of with/witheach rather than the foreach and with structure. Note that it also comes from someone who considers with to be one of the essential features of GML.

As for the keywords: personally, I wouldn't mind if it was just an extension of with (I considered the fact that it doesn't require introducing a new keyword one of selling points from implementation viewpoints). Personally, I don't think silent bugs on with (array) are that much of a threat. From my experience, I never encountered a problem where I'd mistakenly put an array into with and get an error because of that, so I figure this kind of mistake which would turn clear errors into silent bugs is extremely unlikely. (that, and as I mentioned, I find "with (all elements in this array)" to be a very intuitive interpretation, just like "with (all instances of this object)" or "with (all instances whose objects have specific tags)")

If a new keyword was to be introduced after all, I'd likely prefer witheach over with each because some people might want to use each sometimes (e.g. as a name of collection method that checks if each item meets a specific condition).

gnysek commented 1 year ago

If a new keyword was to be introduced after all, I'd likely prefer witheach over with each because some people might want to use each sometimes (e.g. as a name of collection method that checks if each item meets a specific condition).

A good example would be, that people may expect for each to also work, since addition of each.

I have no idea how that works internally and how compiler treats it, but putting space between those two words is a start to suggest things and beliefs, which are not true.

Oracizan commented 1 year ago

Personally, I don't think silent bugs on with (array) are that much of a threat. From my experience, I never encountered a problem where I'd mistakenly put an array into with and get an error because of that, so I figure this kind of mistake which would turn clear errors into silent bugs is extremely unlikely.

I do agree that silent bugs are unlikely in this case, but I still like your suggestion of witheach more.

with, in my mind, actually has nothing to do with iteration. In the statement with(obj), it is obj that produces the iterative behavior (e.g. consider that obj.x = 0 iteratively applies to all instances of obj). What with actually does is adjust the scope of statements within its block - much like it does in many other languages.

So witheach would make sense as an explicit shorthand for iteratively using with with each element of an array. I admit that I don't love it, but I like it most out of all the proposals here.

getting easier semantics for iterating through arrays would be definitely great, but I'd also like the syntactic simplicity of witheach (some_array) {...} compared to foreach (item in some_array) { with (item) { ... } }.

I can respect that. The essence of my argument is this:

GML should definitely add syntactic sugar for array iteration. It is an oft performed, low-level task that is essential to pretty much any language - and it is exactly because it is so low-level and essential that it should not be shackled to with. Why resolve one very specific subset of the issue and leave the general case untouched?

I would not mind witheach so long as foreach or something similar was also implemented. Otherwise it feels like missing the forest for a single tree.

Alphish commented 1 year ago

Generally, I agree foreach would be great to have, alongside with (array)/witheach

I guess one tricky thing would be designing the foreach-like feature. Would it be like foreach (var item in collection) { ... } like in C#? Or should it be more like for (var item of collection) like in JavaScript? It's especially relevant, since GM seems to take lots of inspirations from JS, and doing something like for (var item in collection) would likely mislead people into using a constructor which means something different altogether (iterating over keys of an object, instead of items of an array).

One of the reasons I suggested with (array) specifically is because I believe out of various iterative constructs (with (array), foreach, tags), it seemed like it would incur the least of language/project design work and thus would be the quickest to design and implement out of those three. Of course, I'd love if all three got implemented, as I see them as complementary, rather than one serving as replacement to another.

gnysek commented 1 year ago

Hm, indeed, we're confusing two uses here. with(array) is to change scope, not to get key/value from arrays. I forgot. array should contain references to objects/instances in that case, not random numbers or strings (as for now some resource instances are still integers, but that's slowly changing).

So, foreach is a totally different feature, and now it seems that sth like with references () would be more adequate?

AtlaStar commented 1 year ago

I feel that with is already an overloaded keyword (converts an object reference to a list of instances to loop over + drops you into the instance scope) and I would prefer not to see more functionality added to it.

This is exactly why I think it actually does make perfect sense to iterate over tags; it lets you drop into instance scope of a collection of like objects, that may not be so alike as to need to share an ancestry.

Agree that making with itself work over arrays makes little to no sense though, when what is really desired is a mechanism to iterate over arrays that allows closures of local variables, which currently is cumbersome, and some syntax sugar to drop into an elements scope rather than needing to use explicit access via the dot operator. Since it sounds like closures are on the table with an explicit form of (arg1,arg2,...) => {//expression} all that is really needed is an array_with(array, callback) function that implicitly enters scope so that your callback sig is just () => {//expression}, which is just array_foreach but with the scope entering mechanism, not to make with work over arrays.

katsaii commented 1 year ago

I would like to mention that each will not be a proper keyword. You will still be able to use it as a variable, it will just have special meaning when used immediately after the with keyword.

FoxyOfJungle commented 1 year ago

Hmmm... I personally think a foreach() would be better suited for this, as expected from many other programming languages.

enemies = [obj_enemy1, obj_enemy2, obj_enemy3];
amount = 0;

foreach (enemy in enemies) {
       enemy.visible = true;
       amount++;
}

This syntax is friendlier than using array_foreach() for example, in my opinion, and it doesn't force me to declare a method, even more so if I'm using it in the Step Event.

enemies = [obj_enemy1, obj_enemy2, obj_enemy3];
amount = 0;

array_foreach(enemies, function(enemy) {
    enemy.visible = true;
        amount++;
});

And it would be interesting if foreach() works with structs too.


with() despite being very useful, it's very GML specific, and also, adding more stuff to it may make the function heavier (?).

Explaining how foreach() works would not be a challenge for newbies, if they already have a base of programming, unlike with() and array_foreach() which forces you to use the manual.

Leave with() as is, it already fulfills its current role well :)

Rhyan-eduardo commented 1 year ago

FoxyOfJungle

The use of foreach() instead of array_foreach() or with() offers a simpler and more user-friendly syntax for iterating over elements in a collection, as you exemplified with the code where you set the visibility of enemies.

One advantage of foreach() is that it eliminates the need to declare an additional function, making the code more concise and easier to read. Furthermore, the ability to use foreach() directly in the Step event further simplifies the iteration process.

Your suggestion to allow foreach() to work with structs is also interesting, as it could further facilitate the manipulation of structured data.

Alphish commented 1 year ago

Again, I don't intend with (array) to be a replacement for foreach, but something complementary to it. Also, something that may be easier to implement than foreach because of relatively fewer design decisions and parser work involved.

It isn't aimed at people who want to replace their for (...) loops or array_foreach(...) calls with something neater. It's aimed those who want to replace their with (obj_SomeObject) { ... } with (obj_OtherObject) { ...} Or for (object types for tags) { with (type) { ... } } (alternatively handled by tags becoming their own data type) Or for (components in UI container) { with (component) { ... } }

Basically, the suggestion would benefit people who are already familiar with with construct - and might not necessarily be already familiar with foreach constructs in other languages or even a for loop - and want to handle multiple items with it.

I made a separate feature request (because to my best knowledge no one else did), so please please don't hijack this thread with foreach discussion anymore: https://github.com/YoYoGames/GameMaker-Bugs/issues/3203 ^^'

Gamer-XP commented 1 year ago

A bit unrelated to the feature request, but:

https://github.com/mh-cz/GameMaker-Foreach

There is a macro-based foreach implementation for GM. IT works pretty well, but it breaks GM's syntax highlight a bit. It's also possible to implement custom struct-based collections support for it (with some edits to the source). I wish there was something like this in the native GM, without hacks.

Oracizan commented 1 year ago

@Alphish I do agree that several comments here are misunderstanding this feature request (it is, as you say, not just a for (...) loop) and how with behaves in GML.

That said, the first post described this feature as functionally equivalent to using for (...) with with (...) - so it seems reasonable to me that foreach would come up in discussion if someone agrees the current method is too cumbersome, but doesn't want to see it pared all the way down to with (array).

Please don't read this as hostile to you or this feature - again, I am personally fine with your suggestion of witheach. I only want to suggest that a good-faith comment that misunderstands the feature, or suggests an alternative more concerned with the for (...) part than the with (...) part, is different from hijacking.

Alphish commented 1 year ago

@Gamer-XP If you would like something foreach-like in native GM, please add a thumbsup to the feature request dedicated to foreach and make your comment there: https://github.com/YoYoGames/GameMaker-Bugs/issues/3203

@Oracizan I didn't read these comments as hostile per se, and the mention of hijacking was a slight exaggeration on my part. I admit, I kind of grew tired of people using this feature request to discuss foreach and presenting foreach as a better alternative to witheach rather than a feature that could be implemented in parallel. People did specifically mention it should be made foreach instead of witheach, or that foreach would be better suited for this.

But more importantly, as a separate feature, I figured foreach deserved a feature request of its own. Even more so considering there are various ways the feature could be designed - like C#-style foreach in vs JavaScript-style for of. Then there are different scopes of the feature as well - like custom iterable types or applying the foreach to structs. And those nitty-gritty details belong to a feature discussions of its own, rather than tucked away somewhere in witheach feature discussion.

To sum it up, while being tired of people discussing foreach feature in witheach thread played a big part, the deciding factor behind me posting the request was that it's a feature genuinely worth considering and having plenty of room for discussion of its own. That, and apparently no one else posted foreach request before.

Oracizan commented 1 year ago

@Alphish Thanks for your thoughtful response.

foreach on its own does not/should not have the same functionality as witheach. I am with you there, and I don't support any suggestions with that framing of the feature.

foreach (...) with (...) is a valid alternative that is less tedious than the current way of doing things. I accept that it stills feels too involved to some, and witheach seems to be the most viable compromise.

I am less (but not none) concerned with how easy or quick a feature is to implement, and more concerned with the long-term impact on the language. with is a really, really low-level GML feature and any changes to it are almost philosophical in nature. This is similar to the distinction you make between in vs of; these low-level keywords mean very specific things. with has parallels in many languages (VBA, Javascript) and it performs the same narrow function (adjusts the scope of statements) in each. My priority is avoiding its expansion in a way that would make the programmer's intent ambiguous or back GML into a corner with future language changes - what if we want arrays to have a scope and properties, like Javascript, or what if we want to treat a struct as iterable?

A new keyword witheach alleviates these concerns nicely by making programmer intent explicit. So this is not an argument against the feature as a whole, just an explanation of where I'm coming from in my previous comments and a vote for this specific implementation of the feature.

I think I've expressed myself as best as I'm able at this point, so I'll leave further discussion to others.

gnysek commented 1 year ago

To clarify, as I understand, whole request about with each isn't to iterate over each element and get it's value (as we're even not telling to which variable key/value could be assigned), it's rather a shorthand for changing context of current object/instance (what with does now):

var array = [obj_player, obj_wall, obj_door];

// new way:
with each (array) { /* code here */ }

// same as:
for(var i = 0, j = array_length(array); i < j; i++) {
    with(array[i]) {
        /* code here */ 
    }
}

When tags would be a hash/reference instead of string, this could be very useful to have several grouping options of objects in GML.

foreach is something else, even if it looks similar, but only common thing is that it iterates over all elements... That's how I understand this.

So:

with (struct, object or instance, or keyword all/other) - changes context and executes code in scope of what's inside brackets with each (array of structs, objects or instances or keyword all/other) - same as above, but does it for each element in array (one after one)

anabah122 commented 1 year ago

lol f = func () {} with a f with b f

backYard321 commented 1 year ago

Again, I don't intend with (array) to be a replacement for foreach, but something complementary to it. Also, something that may be easier to implement than foreach because of relatively fewer design decisions and parser work involved.

It isn't aimed at people who want to replace their for (...) loops or array_foreach(...) calls with something neater. It's aimed those who want to replace their with (obj_SomeObject) { ... } with (obj_OtherObject) { ...} Or for (object types for tags) { with (type) { ... } } (alternatively handled by tags becoming their own data type) Or for (components in UI container) { with (component) { ... } }

Basically, the suggestion would benefit people who are already familiar with with construct - and might not necessarily be already familiar with foreach constructs in other languages or even a for loop - and want to handle multiple items with it.

I made a separate feature request (because to my best knowledge no one else did), so please please don't hijack this thread with foreach discussion anymore: YoYoGames/GameMaker-Bugs#3203 ^^'

I am 100% in agreement with this; being able to write with variable where variable can be an instance id/struct/object or an array of these would create a lot of flexibility. I'm not satisfied with using foreach() either, since you can't really use it in conjunction with local variables, and you lose access to things like break continue etc.

I think this especially makes sense since instance id's and objects are refs now instead of numbers. with each would be an okay compromise, but I'm way more in favour of just with, as I use it for pretty much everything and it would make arrays a lot more elegant (which have already gotten a lot of improvement over the past few updates).

zzhamzz commented 1 year ago

I would be against this mutation of the with keyword.

  1. array_foreach already does everything OP wants from the feature request (and way more)
  2. with changes scope context, its purpose is/should not be iterating over data structure elements.
  3. we have array_foreach, string_foreach, and struct_foreach that already do this. If we want a language keyword to mimic these it should be 'foreach'
  4. What would be the syntax of this? It isn't proposed in any part of this discussion. More about the follows.

As evidenced by OPs further replies this request is made with the assumption that it would work with array that each element happens to be scope-able context.

IMO that is simply too narrow of a case to add clutter to the language and MORE IMPORTANTLY: array_foreach already exists and does everything they want, and foreach is a staple keyword in many languages. No reason to hi-jack the very specific GML use case of with

OP states this is supposed to be replacing the for loop: e.g. for(_i = 0; _i < array_length; _i++)

But again with changes scope context so the code you put in the brackets after with applies to that scope:

e.g. with(someScopeContext) { // do stuff in that scope }

If we did this to an array are we assuming each element of the array is a scope-able context? If not a scope-able context does it:

array_foreach is already flexible enough to handle all that; scope-able context, not scope, could skip it, could error etc..

If it is supposed to be replacing for(_i = 0; _i < array_length; _i++) then how do we write something as simple as:

for(_i = 0; _i < array_length; _i++) { myArray[_i]++; }

as a with statement?

with(myArray) { // what do I write here to increment each element // self++ ??? }

No, of course we don't. Becuase feature request isn't for this, its for a super narrow use case. What happens when someone tries to use it outside of that narrow case?

The array_foreach accepts a function callback that takes two arguments 'element' and 'index'. How would we get 'element' and 'index' inside the with keyword brackets? Again, we don't. Because this isn't even what OP wants. They want a singular use case that is already covered in a number of ways; i.e. you can iterate over arrays that happen to contain scope-able context.

The current array_foreach can work with any type of element in the array AND it can can change scope if you want it to. How could with do that without a bunch of extra syntax?

In my opinion this is simply uneeded and pure clutter. Either use a traditional loop (for, while etc..), or the existing *_foreach library functions or add a foreach keyword that mimics array_foreach (but honestly... why?)

As far as the intent of providing a way to iterate over custom list/collections of scope-able context; I say that is a good idea. But not this specific suggestion; with working with 'tags' would be better.

Alphish commented 1 year ago

@zzhamzz You already know I'm referring to a specific use-case, so why spend the better half of your comment describing the odd use cases I never intended in the first place? >.<

This suggestion came from a simple intuition of with ([this, that, another]) corresponding to English-like sentence "with this and with that and with another" or with (child_components) corresponding to English-like sentence "with each of child_components".

The problem isn't people potentially misusing the feature - for each feature you will always find a way to misuse it, e.g. and operator is misused like if (some_variable != left_option and right_option). If mere potential for misuse was enough to axe a feature, we might as well make a programming language where the only operation available is printing "Hello, world!".

The problem is people instinctively misusing the feature because it's unintuitive. I wrote the feature request with assumption that many people would understand the analogy to English-like sentences I mentioned above, but considering this whole discussion, maybe it's not as intuitive as I'd hoped. So I don't really care about this feature anymore...

(just for the record: current array_foreach is far from a perfect replacement for for{with{}}, because unlike for and with blocks it doesn't receive temporary variables from outside the function; that's also why in many cases I still use for over array_foreach)

backYard321 commented 1 year ago

I would be against this mutation of the with keyword.

1. array_foreach already does everything OP wants from the feature request (and way more)

2. `with` changes scope context, its purpose is/_should_ not be iterating over data structure elements.

3. we have array_foreach, string_foreach, and struct_foreach that already do this. If we want a language keyword to mimic these it should be 'foreach'

4. What would be the syntax of this? It isn't proposed in any part of this discussion.  More about the follows.

As evidenced by OPs further replies this request is made with the assumption that it would work with array that each element happens to be scope-able context.

IMO that is simply too narrow of a case to add clutter to the language and MORE IMPORTANTLY: array_foreach already exists and does everything they want, and foreach is a staple keyword in many languages. No reason to hi-jack the very specific GML use case of with

OP states this is supposed to be replacing the for loop: e.g. for(_i = 0; _i < array_length; _i++)

But again with changes scope context so the code you put in the brackets after with applies to that scope:

e.g. with(someScopeContext) { // do stuff in that scope }

If we did this to an array are we assuming each element of the array is a scope-able context? If not a scope-able context does it:

* just skip them?

* error?

* exception?

* How could the user choose the behavior?

array_foreach is already flexible enough to handle all that; scope-able context, not scope, could skip it, could error etc..

If it is supposed to be replacing for(_i = 0; _i < array_length; _i++) then how do we write something as simple as:

for(_i = 0; _i < array_length; _i++) { myArray[_i]++; }

as a with statement?

with(myArray) { // what do I write here to increment each element // self++ ??? }

No, of course we don't. Becuase feature request isn't for this, its for a super narrow use case. What happens when someone tries to use it outside of that narrow case?

The array_foreach accepts a function callback that takes two arguments 'element' and 'index'. How would we get 'element' and 'index' inside the with keyword brackets? Again, we don't. Because this isn't even what OP wants. They want a singular use case that is already covered in a number of ways; i.e. you can iterate over arrays that happen to contain scope-able context.

The current array_foreach can work with any type of element in the array AND it can can change scope if you want it to. How could with do that without a bunch of extra syntax?

In my opinion this is simply uneeded and pure clutter. Either use a traditional loop (for, while etc..), or the existing *_foreach library functions or add a foreach keyword that mimics array_foreach (but honestly... why?)

As far as the intent of providing a way to iterate over custom list/collections of scope-able context; I say that is a good idea. But not this specific suggestion; with working with 'tags' would be better.

I outlined why array_foreach() is limited and not ideal to use - as Alphish and I mentioned, you can't use local variables from outside of the method you pass in, and you can't use continue/break or exit out of the script/event. And you wouldn't write anything to increment each element, that's the whole point - it takes an array of scope-able variables and runs code on all of them, similar to how with object goes over every instance in the game. In the case of objects, you COULD write:

for (var i = 0; instance_number(object); i++)
{
    with instance_find(object, i)
    {
        // code
    }
}

But instead you can just do with object { }, and 0 people have a problem with that because it's straightforward and clear. Similarly, I think it's inelegant to write:

array_foreach(array, function(_instance)
{
    with _instance
    {
        // code
    }
}

Again I agree with Alphish; I think this is a really natural extension of with and makes a lot of intuitive sense. I would love to see this paired with something like instance_place_array(), because to be honest having to rely on lists for collision functions that take into account more than one collision is not ideal. And incidentally, I've talked to a lot of other devs who agree; I respect what the other replies here are saying, but most of them also want a better foreach option - those aren't mutually exclusive.

zzhamzz commented 1 year ago

@Alphish The problem isn't people potentially misusing the feature

Correct. The bigger issue is its lack of usability. The feature is suggesting that with accept arrays. But It can't operate on any array or array element; only elements that `with' could normally act upon.

The fact that it provides almost no ability for the user to error check their misuse within the with brackets makes it a big thumbs down for me. Even a try/catch block would be near useless in this situation.

I understand you guys like closures. But this ain't the way to go about it.

backYard321 commented 1 year ago

@Alphish The problem isn't people potentially misusing the feature

Correct. The bigger issue is its lack of usability. The feature is suggesting that with accept arrays. But It can't operate on any array or array element; only elements that `with' could normally act upon.

The fact that it provides almost no ability for the user to error check their misuse within the with brackets makes it a big thumbs down for me. Even a try/catch block would be near useless in this situation.

In fairness, doesn't looping through the array and using with on every entry have the exact same issue? What's the error checking on that?

zzhamzz commented 1 year ago

@BackYard321

with currently acts apon a single item or an object_index which is gauranteed to point ot a list of instances. If not then there is a serios issue way beyond the user level. The fact that with operates on an internal list beyond the user level is what makes with acceptable at all.

In the case of a single item, that is easy to error check immediately before you call with.
For an object_index there is no possibilty of an element not being the correct type. Every element with will iterate through is guaranteed to be a scopeable context.

For arbitary list of arbitrary items, how do you error check? And how would you do so in a recoverable way?

A for loop lets you handle it inside the loop. array_foreach lets you handle it inside the loop. withdoes not.

backYard321 commented 1 year ago

@zzhamzz

I see your point! I'm inclined to say that it should just skip over anything that isn't scopeable, similar to how with undefined and with noone are skipped over now (when in previous versions they threw an error). That being said, I feel like it might be too crazy to skip ANY non-scopeable data type instead of throwing an error (EG a string), so yeah I don't have a definitive solution for that.

Although: doesn't the same problem exist for all collision functions that currently accept arrays? Those effectively loop through arrays, and it's not really possible to error check those in a recoverable way (unless I'm getting this wrong).

Alphish commented 1 year ago

@zzhamzz with allows you to handle errors before the loop, just like the for and array_foreach. In fact, handling errors outside the loop might be beneficial, because you won't accidentally process a few first withables, run into an error with non-withable and leave the subsequent valid withables unprocessed.

if (array_any(my_items, is_not_withable))
    throw "Hold it, you're operating on non-withable items here!";

with (my_items) { ... }

As for error-handling: I'd expect that if with (some_thing) { ... } throws an error for a given value of some_thing, then with ([some_thing]) { ... } should throw a similar error. If the dev wants to skip over such items instead, they can run array_filter beforehand or something.

Also, lots of complaints here could be said about 2023.8 array-accepting collision functions:

Not saying every complaint is automatically invalid, and it may be the case that the intuition of expanding with to accept arrays of withables isn't as clear as expanding collision functions to accept arrays of collideables. But I'd consider whether a given complaint about with (array) { ... } could be used to rejecting passing arrays into collision functions. From what I've seen, they've been very warmly received, and I haven't heard complaints about users not understanding how to work with them.

mistletoe commented 10 months ago

I have serious reservations in regards to with(array) as proposed by the OP.

It's syntactic sugar, and that's fine. However, it's potentially confusing for developers.

Let's just start with why this is a good idea, in terms of syntactic sugar. This is about saving some time typing out:

for(var i = 0; i < array_length(myArray) - 1; i++){ var blah = myArray[i]; do_stuff_to_blah(blah); }

Which, to be sure, takes a little time, and it's something we have to do pretty often. But using with() isn't the same at all, and the difference shouldn't be muddied.

with() currently is fairly sensible, because it's an iterator over objects (in the programming sense). Each object's pointer is available; now we have a reference and can query deeper in. Using it on Structs makes sense; they're objects. Using it on Instances makes sense; ditto. ds_lists? Same; they're key/value objects.

Tags? Uh oh. Tags are just a string array maintained somewhere in GMS, presumably. That sounds like a dubious idea, but maybe, as a special case.

Arrays, though? Now I'm really dubious, because it's not a special case GMS can make safe assumptions about.

The problem is straightforward. Arrays can be full of whatever you want. Arrays aren't objects, and aren't guaranteed to contain any.

They may contain pointers to objects, but they may not, and any proposed with(array) would have to deal with this, which would be expensive and make it of limited utility. GML doesn't require Arrays to be typed, so there's no way to guarantee anything; the first entry in an array may be an ordinary integer or float, the second one might be a pointer to an Instance, and so forth.

So I definitely don't like with(array) as a concept, for the above reasons.

But how about a non-confusing syntactic sugar that gets the job done?

iterate(array,var) would work. Used in the following way:

iterate(myArray, i){ do_something_to_i(i); }

Most of the typing's gone, from the first example.

I'd propose reverse_iterate(array) as well, for obvious reasons.

So, basically, this would get rid of the confusion and other issues with the feature request:

  1. iterate(array,var) would obviously only work on Arrays, where the contents of any given position, whilst numeric, might be anything at all, from the reference to a Color object's pointer to a simple boolean.
  2. It wouldn't create confusion by pretending to be "with(), but different".
  3. Users would be kept aware that they're using syntactic sugar specific to arrays.
  4. It doesn't have to have lots of GML interpreter bloat behind the scenes "figuring out" the context of with() when run or compiled.
backYard321 commented 10 months ago

I have serious reservations in regards to with(array) as proposed by the OP.

It's syntactic sugar, and that's fine. However, it's potentially confusing for developers.

Let's just start with why this is a good idea, in terms of syntactic sugar. This is about saving some time typing out:

for(var i = 0; i < array_length(myArray) - 1; i++){ var blah = myArray[i]; do_stuff_to_blah(blah); }

Which, to be sure, takes a little time, and it's something we have to do pretty often. But using with() isn't the same at all, and the difference shouldn't be muddied.

with() currently is fairly sensible, because it's an iterator over objects (in the programming sense). Each object's pointer is available; now we have a reference and can query deeper in. Using it on Structs makes sense; they're objects. Using it on Instances makes sense; ditto. ds_lists? Same; they're key/value objects.

Tags? Uh oh. Tags are just a string array maintained somewhere in GMS, presumably. That sounds like a dubious idea, but maybe, as a special case.

Arrays, though? Now I'm really dubious, because it's not a special case GMS can make safe assumptions about.

The problem is straightforward. Arrays can be full of whatever you want. Arrays aren't objects, and aren't guaranteed to contain any.

They may contain pointers to objects, but they may not, and any proposed with(array) would have to deal with this, which would be expensive and make it of limited utility. GML doesn't require Arrays to be typed, so there's no way to guarantee anything; the first entry in an array may be an ordinary integer or float, the second one might be a pointer to an Instance, and so forth.

So I definitely don't like with(array) as a concept, for the above reasons.

But how about a non-confusing syntactic sugar that gets the job done?

iterate(array,var) would work. Used in the following way:

iterate(myArray, i){ do_something_to_i(i); }

Most of the typing's gone, from the first example.

I'd propose reverse_iterate(array) as well, for obvious reasons.

So, basically, this would get rid of the confusion and other issues with the feature request:

1. iterate(array,var) would obviously only work on Arrays, where the contents of any given position, whilst numeric, might be anything at all, from the reference to a Color object's pointer to a simple boolean.

2. It wouldn't create confusion by pretending to be "with(), but different".

3. Users would be kept aware that they're using syntactic sugar specific to arrays.

4. It doesn't have to have lots of GML interpreter bloat behind the scenes "figuring out" the context of with() when run or compiled.

Respectfully, most of if not all of the points you've made have been raised previously and responded to in this thread (also, I don't think what you're saying about ds_lists is accurate). There are built-in functions such as instance_place() that allow you to pass in arrays of instances/objects, which has the same potential problem you're outlining ("arrays can contain anything"), but it's been an undisputed positive change as I haven't seen other users actually experience this problem in practice.

with array would also allow you to expand code normally reserved for single objects/instances/structs, etc, without having to make special cases for arrays of them, and therefore fewer points of failure for your code. It's more than just "saving some time" typing out for loops.

Also, regarding "iterate()": refer to YoYoGames/GameMaker-Bugs#3203.

mistletoe commented 10 months ago

Respectfully, most of if not all of the points you've made have been raised previously and responded to in this thread (also, I don't think what you're saying about ds_lists is accurate). There are built-in functions such as instance_place() that allow you to pass in arrays of instances/objects, which has the same potential problem you're outlining ("arrays can contain anything"), but it's been an undisputed positive change as I haven't seen other users actually experience this problem in practice.

with array would also allow you to expand code normally reserved for single objects/instances/structs, etc, without having to make special cases for arrays of them, and therefore fewer points of failure for your code. It's more than just "saving some time" typing out for loops.

Also, regarding "iterate()": refer to YoYoGames/GameMaker-Bugs#3203.

I apologize if this felt like it was wasting your time.

You're right that ds_list and so forth also have the "anything might be in there" problems; I'd really like to reduce same in GML and especially get rid of "data is ambiguous" issues in the GML interpreter, where it's causing needless drag.

Maybe there's a good middle ground, where with() can be used without ambiguity. A second argument could be passed to it, to set type:

with(array,data_type) where data_type would be any of the valid data types might work best?

This would get rid of my objections, I think, and make with() operate just as quickly on an array, ds_list, etc. as it does when passed an Object ID. The general problem here is that:

A. When iterating over data structures, with() should not generally have to do type-determination work on each element unless the user doesn't bother giving the GML interpreter instructions.

B. When with() has the second parameter, then further instructions within the with() loop don't need to do type determination at all, because the variable has already been cast to the type specified. They still have to handle errors (i.e., sending a negative number to an Instance operation, etc.) but they don't have to do lots of try-->catch. I presume there's already support within GMS for "don't bother doing type-checking on this data" for all the operations.

If I was always able to specify, "do not attempt interpretation, data type is valid" on operations in general, I'd use that all the time to cut down on GML interpreter bloat.

backYard321 commented 10 months ago

Respectfully, most of if not all of the points you've made have been raised previously and responded to in this thread (also, I don't think what you're saying about ds_lists is accurate). There are built-in functions such as instance_place() that allow you to pass in arrays of instances/objects, which has the same potential problem you're outlining ("arrays can contain anything"), but it's been an undisputed positive change as I haven't seen other users actually experience this problem in practice. with array would also allow you to expand code normally reserved for single objects/instances/structs, etc, without having to make special cases for arrays of them, and therefore fewer points of failure for your code. It's more than just "saving some time" typing out for loops. Also, regarding "iterate()": refer to YoYoGames/GameMaker-Bugs#3203.

I apologize if this felt like it was wasting your time.

You're right that ds_list and so forth also have the "anything might be in there" problems; I'd really like to reduce same in GML and especially get rid of "data is ambiguous" issues in the GML interpreter, where it's causing needless drag.

Maybe there's a good middle ground, where with() can be used without ambiguity. A second argument could be passed to it, to set type:

with(array,data_type) where data_type would be any of the valid data types might work best?

This would get rid of my objections, I think, and make with() operate just as quickly on an array, ds_list, etc. as it does when passed an Object ID. The general problem here is that:

A. When iterating over data structures, with() should not generally have to do type-determination work on each element unless the user doesn't bother giving the GML interpreter instructions.

B. When with() has the second parameter, then further instructions within the with() loop don't need to do type determination at all, because the variable has already been cast to the type specified. They still have to handle errors (i.e., sending a negative number to an Instance operation, etc.) but they don't have to do lots of try-->catch. I presume there's already support within GMS for "don't bother doing type-checking on this data" for all the operations.

If I was always able to specify, "do not attempt interpretation, data type is valid" on operations in general, I'd use that all the time to cut down on GML interpreter bloat.

I don't really understand the purpose of changing with to work like a built-in function, or otherwise having both with and with(). The implication of using with array is that you're deliberately treating every array entry as something that can be "scoped" (ie anything associated with a self). So treating with as a function and passing in an additional argument based on datatypes just sounds redundant (especially since, again, with array could use an array populated with several different scope-able datatypes, the same way instance_place() does).

Frankly I also don't know what data you have access to where you're talking about "needless drag" and "interpreter bloat", unless this is just meant as speculation.