Auto-Boxing, Equals and all that.

Monkey Forums/Monkey Programming/Auto-Boxing, Equals and all that.

muddy_shoes(Posted 2011) [#1]
The docs are a bit quiet on the subject of Monkey's auto-boxing and as I tripped over it while porting box2d I'm wondering if we could get some clarification about intentions.

It looks like Monkey is following the Java model, is that the case? If so, then are you also going to uncomment the Equals method on the Object definition so we have a standard way to test equality by value?

Also, there seems to be an inconsistency with the Java model when handling overloaded method resolution. Java will use a method that doesn't require boxing/unboxing in preference to one that does. Monkey currently throws an error if you have an overload that takes an Object reference as well as one that takes the unboxed type and call with the boxed type.

I'll put some test code here that shows the problem as well as demonstrating the other behaviours for anyone interested:

Class AutoBoxTest
	
	Method Test(val:IntObject)
		Print "IntObject method called"
	End
	
	Method Test(val:Int)
		Print "Int method called"
	End

End
Class AutoBoxTest2
	
	Method Test(val:Object)
		Print "Object method called"
	End
	
	Method Test(val:Int)
		Print "Int method called"
	End

End

Class BoxyCollection<T>
    
    Private
    Field arr : T[] = New T[10]
   
    Public
    Method Get:T( index:Int)
        If( index >=0 And arr.Length > index )
            Return arr[index]
        Else
            Return Null
        End
    End
    
    Method Set( index:Int, item:T )
        If( index >= arr.Length )
            arr = arr.Resize(index+10)
        End
        arr[index] = item
    End
End

Function Main()
	Local io1:IntObject = 1
	Local io2:IntObject = 1

	Local ip1:Int = io1
	Local ip2:Int = io2
	
	Local tester:AutoBoxTest = New AutoBoxTest()
	
	tester.Test(io1)
	tester.Test(ip1)
	
	If io1 = io2
		Print "IntObjects are considered equal by value"
	Else
		Print "IntObjects aren't considered equal by value"
	End
	
	If io1 = ip1
		Print "IntObject and Int primitive are considered equal"
	Else
		Print "IntObject and Int primitive are considered unequal"
	End

	Local bc:BoxyCollection<IntObject> = New BoxyCollection<IntObject>()
	
	bc.Set(0,ip1)
	If bc.Get(0) = ip1
		Print "Int set and retrieved and compared successfully"
	Else
		Print "Int set and retrieved and comparison failed"
	End
	
	Local tester2:AutoBoxTest2 = New AutoBoxTest2()
	
	tester2.Test(io1) 'Unable to determine overload
	tester2.Test(ip1)
	
End



marksibly(Posted 2011) [#2]
Hi,

The boxing stuff is based on my experience with Java about 10 years ago!

But it's not really based on Java's 'model' or anything, I just did it the way I thought would be most useful. In Java, I seem to remember you couldn't do this...

IntObject x=10;

...but maybe you can now.

> Also, there seems to be an inconsistency with the Java model when handling overloaded method resolution.

I prefer it Monkey's way, as there's absolutely no confusion and keeps the overloading rules simple. And in the real world, wouldn't/shouldn't the method param be IntObject instead of Object anyway?

But perhaps a modification could be made to the overloading rules, eg: a single 'upcast' param match could be considered an exact match? This is something I've considered for other reasons too, and it should work here too.


muddy_shoes(Posted 2011) [#3]
To be honest, I find the idea of having to cast a descendant of Object to Object in order to persuade the language that I actually believe it is what it is to be confusing. If I want to pass the value to the primitive overload then the ToPrimitive() methods there to do just that.

Anyway, opinions aside, the request for some sort of clarifying documentation stands.

Edit: And the question about the Equals method.


Samah(Posted 2011) [#4]
Since Java 5, the compiler has autoboxed/unboxed for you. So yes you can do both:

Integer x = 10;
int y = x;

Just a little tip when manually boxing integers: Never use new Integer(int) because it ALWAYS creates a new object. Java keeps a pool of Integer objects for you to reuse, so always use Integer.valueOf(int). It'll read from the pool first, or create one if necessary.


muddy_shoes(Posted 2011) [#5]
I'm afraid I'm going to pick up on this again, because it's niggling me.

The attraction of auto-boxing and unboxing is meant to be that you can use wrappers for primitives without the annoying overhead of typing the conversions all over the place. So, in theory, it's great for something like a general data wrapper because you can add the "ToPrimitive" methods and a bunch of overloaded constructors/factory methods to take the primitives and life becomes very easy. For example, with a JSONObject defined in this way, I can do the following:

Local root:JSONObject = New JSONObject()
root.AddItem("name",name) 'name is a String
root.AddItem("width",width) 'width and height are Floats
root.AddItem("height",height)
root.AddItem("bounded",bounded) 'bounded is a Bool

name = root.GetItem("name")
width = root.GetItem("width")
height = root.GetItem("height")
bounded = root.GetItem("bounded")


Good stuff. Super easy to use. However, like most general-purpose data-structures, JSON is recursive. A JSON object or array can contain JSON objects and arrays. So, I need to be able to add those too.

Local playerJSON:JSONObject = New JSONObject()
playerJSON.AddItem("posX",player.position.x)
playerJSON.AddItem("posY",player.position.y)
root.AddItem("player",playerJSON)


Well, that would be nice, except I can't, because Monkey gets its pants in a knot over the fact that there are methods that take primitives as well as one that matches the unboxable object type. Instead, I have to have separate method names for primitives. This is not pretty, less easy to use, seems to go against the very reason for having auto-boxing in the first place and generally makes me an unhappy panda.