TGadget array iterates improperly!

BlitzMax Forums/BlitzMax Programming/TGadget array iterates improperly!

jondecker76(Posted 2010) [#1]
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



plash(Posted 2010) [#2]
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).


jondecker76(Posted 2010) [#3]
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!)


jondecker76(Posted 2010) [#4]
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


plash(Posted 2010) [#5]
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.


jondecker76(Posted 2010) [#6]
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


plash(Posted 2010) [#7]
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.


jondecker76(Posted 2010) [#8]
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!


plash(Posted 2010) [#9]
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.


Czar Flavius(Posted 2010) [#10]
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.