TGadget array iterates improperly!
BlitzMax Forums/BlitzMax Programming/TGadget array iterates improperly!
| ||
In a project that I am currently working on, I have an array of TGadgets that must be dynamically resized. After increasing the size of the array, the .length field shows correctly, but the EachIn iteration is stuck at the original size of the array! (See full example below) Note: I have tested this on an array of strings and it works fine, so it must be something specific to the TGadget iterator SuperStrict Import MaxGui.Drivers 'Press the "Show Bug" button to show that after incrementing 'an array of TGadgets, the eachin iterator does not work properly! 'Press the button multiple times to see the result! 'Make the window Global win:TGadget=CreateWindow("Skin Editor",0,0,640,480,Null,WINDOW_TITLEBAR|WINDOW_MENU|WINDOW_STATUS|WINDOW_CENTER|WINDOW_ACCEPTFILES) 'Create an array of 5 gadget Global gadgets:TGadget[5] For Local i:Int=0 To 4 gadgets[i]=CreateTextField(10,30*i,50,20,win) SetGadgetText(gadgets[i],String(i)) Next Global btnShowBug:TGadget = CreateButton("Show Bug",20,200,100,24,win) While WaitEvent() Select EventID() Case EVENT_GADGETACTION Select EventSource() Case btnShowBug incrementArray() End Select Case EVENT_WINDOWCLOSE End End Select Wend Function incrementArray() Print "Length of array: " + gadgets.Length For Local thisGadget:TGadget = EachIn gadgets Print ".."+GadgetText(thisGadget) Next 'Now lets increment the size of the array! gadgets=gadgets[..gadgets.length+1] Print "Length of array: " + gadgets.Length For Local thisGadget:TGadget = EachIn gadgets Print ".."+GadgetText(thisGadget) Next Print "Done!" End Function |
| ||
This is no bug. You're not getting the last value because it is Null. The String array works differently because technically Null is a String (an empty one, but a String nonetheless). |
| ||
I totally disagree. A null string is no more a real string than any other null object is a real object. Even still, it shouldn't be correct behavior. For example, what if I wanted to iterate through the array so that I can set them to not be null! Sure, I can use a for loop with an index integer, but still, Eachin should return every element of an array, initialized or not (its the programmer's job to have clean code, not the job of the language!) Take this case example where I want to call a function which increments an array, then iterates through it to create all new objects for it: Function incrementArray(gadgets:TGadget[]) 'Now lets increment the size of the array! gadgets=gadgets[..gadgets.length+1] 'Now, lets iterate through the list to create all new gadget instances! local i:int=0 for this:TGadget = eachin gadgets) this=createTextField(10,30*i,50,20,win) next 'Doh! Won't work because eachin is not ruturning null gadgets! End Function Above is a perfect example of why eachin should return ALL items in the array, not just initialized ones! Then, the programmer can use code to determine how to deal with null instances... if not this=null then..... instead of having such a quirk in the language (that isn't even handled consistently amongst different object types!) |
| ||
To explain a bit more: It would make sense to skip null objects if they are of the generic Object type, as a null object could be any type at all. But in the sace of an array, where all objects are of the same type, null objects should be enumerated. Oh well, I will just use a for loop with an index |
| ||
Take this case example where I want to call a function which increments an array, then iterates through it to create all new objects for it That isn't doing what you think it's doing. You're setting the variable this to a new gadget instance, but not the element in the array. You have to manually iterate the array if you want to do something like that (also, incrementing your own index within an EachIn loop will not work if there are Nulls in the array, as you will be addressing the wrong element eventually). It would make sense to skip null objects if they are of the generic Object type, as a null object could be any type at all. Null is an Object (and in the case of String, a special value), not a TGadget or any other type. |
| ||
You're setting the variable this to a new gadget instance, but not the element in the array Aren't all abject variables pointers? I would think that in an eachin loop, each object iterated would point to exactly the same memory location. In fact, just to entertain myself, I made a quick example, and you can most definitely use objects returned with eachin to modify the original target object - just see the example below: SuperStrict Type Ttxt Field str:String End Type 'create 5 Ttxt instances in an array Global txtArray:Ttxt[5] For Local i:Int=0 To 4 Local thisTxt:TTxt=New Ttxt thisTxt.str="String "+Rand(1,1000) txtArray[i]=thisTxt Next 'Now print them! For Local this:Ttxt=EachIn txtArray Print this.str Next 'now change them! For Local this:Ttxt=EachIn txtArray this.str="Different " +Rand(1,1000) Next 'And print them again For Local this:Ttxt=EachIn txtArray Print this.str Next 'Yep, they changed! Null is an Object (and in the case of String, a special value), not a TGadget or any other type. Strings are objects just as well as TGadgets, TImages etc.. (more specifically, a string array, which is why you can slice strings or refer to individual characters by its index in brackets)- backing up my point that eachin object enumeration is not consistent amongst ALL object types |
| ||
Your second example is not creating any new instances in the EachIn loop, which you were doing in your first example. If you were to try this, you'll see it does not work: Strings are objects just as well as TGadgets, TImages etc.. Yes, they are. But an Object (Null) is not a TGadget.Now I might be wrong in that the loop isn't giving you the Null because it is not a TGadget; if so, EachIn just ignores Null for non-String arrays (where Null is acting as an empty String). (more specifically, a string array, which is why you can slice strings or refer to individual characters by its index in brackets) Not quite. backing up my point that eachin object enumeration is not consistent amongst ALL object types The only difference seen is with the standard datatypes (Int, Float, String), which are immutable, whereas all Objects other than String are not. |
| ||
The only difference seen is with the standard datatypes (Int, Float, String), which are immutable, whereas all Objects other than String are not. Ok, this makes more sense now. So I guess this isn't so much a bug report. But I still think its a valid feature request to level the playing field across all objects (string or not). It just doesn't make sense to me that I can do a: mylist:TList=CreateList() mylist.addlast(string(null)) 'works just fine 'but not a mylist.addlast(myObject(null)) ' error about inserting a null object So I guess I now understant that although strings are objects, they have their own set of rules that are different than every other object in BMX (both in that iterators behave differently regarding strings than all other objects, and that all other objects get tossed around as pointers and string objects are not). I can live with this just fine now that I know about it, its just counter-intuitive. Plus, it makes it hard to prototype blocks of code using only string objects with the goal of modifying it later to work on your own custom objects (which is what led me here to report the "bug" in the first place) Thanks for the clarification! |
| ||
It just doesn't make sense to me that I can do a The reason is Null acts differently when used as a String (as stated previously). bbEmptyString (which is a static variable on the C side of the core modules) is actually used in-place of Null when there is a Null cast to a String. Plus, it makes it hard to prototype blocks of code using only string objects with the goal of modifying it later to work on your own custom objects You should use index iteration if you want to change an element's value. EachIn is only an enumerator. |
| ||
Strings are supposed to be a hybrid between an object and a simple data type. The alternative would be for "" and Null to be two different things, but to make things easier they are the same. That's why you can mess around with Null strings in this way but not Null objects. |