(Number, Number) matches (Float, Int) but does not match (Int, Float)
Is it a bug in Scala 2.8.0 ? (the same happens with 2.8.1.RC2)
import junit.framework._ import Assert._ class BugTest extends TestCase { def compare(first: Any, second: Any): Int = { (first, second) match { case (k: Int, o: Int) => k compare o //why the next case matches (Float, Int) but does not match (Int, F开发者_如何学运维loat) ??? case (k: Number, o: Number) => k.doubleValue() compare o.doubleValue() case _ => throw new Exception("Unsupported compare " + first + "; " + second) } } def testCompare() { assertEquals("Both Int", -1, compare(0, 1)) assertEquals("Both Float", 1, compare(1.0, 0.0)) assertEquals("Float then Int", 0, compare(10.0, 10)) assertEquals("Int then Float", 0, compare(10, 10.0))//this fails with an exception } }
I think this is a bug. If you look at the generated bytecode:
public int compare(java.lang.Object, java.lang.Object);
Code:
Stack=4, Locals=7, Args_size=3
0: aload_1
1: astore_3
2: aload_2
3: astore 4
5: aload_3
6: instanceof #8; //class java/lang/Integer
9: ifeq 81
12: aload_3
13: invokestatic #14; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
16: istore 5
18: aload 4
20: instanceof #8; //class java/lang/Integer
23: ifeq 45
26: getstatic #20; //Field scala/Predef$.MODULE$:Lscala/Predef$;
29: iload 5
31: invokevirtual #24; //Method scala/Predef$.intWrapper:(I)Lscala/runtime/RichInt;
34: aload 4
36: invokestatic #14; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
39: invokevirtual #29; //Method scala/runtime/RichInt.compare:(I)I
42: goto 124
45: new #31; //class java/lang/Exception
//part omitted for brevity..
81: aload_3
82: instanceof #54; //class java/lang/Number
85: ifeq 161
88: aload_3
89: checkcast #54; //class java/lang/Number
92: astore 6
94: aload 4
96: instanceof #54; //class java/lang/Number
99: ifeq 125
102: getstatic #20; //Field scala/Predef$.MODULE$:Lscala/Predef$;
105: aload 6
107: invokevirtual #58; //Method java/lang/Number.doubleValue:()D
110: invokevirtual #62; //Method scala/Predef$.doubleWrapper:(D)Lscala/runtime/RichDouble;
113: aload 4
115: checkcast #54; //class java/lang/Number
118: invokevirtual #58; //Method java/lang/Number.doubleValue:()D
In line 6, the check is done whether the first variable is an instance of java.lang.Integer
. If this does not succeed, we continue at line 81, which starts with the java.lang.Number
check. If the first variable is an Integer, then we continue with the same check for the second variable. However, if this second check does not succeed, instead of again continuing at line 81 with the Number check, a jump is made to line 45, which throws the Exception. That does not seem to be correct. I quickly browsed through trac but couldn't directly find an issue regarding this, so it may be sensible to create one.
Edit As Extempore pointed out, it is a bug that is already in trac, see his comment below.
I don't know the answer to the specific question, but here's a different way to define compare:
def compare[A : Numeric, B: Numeric](first: A, second: B): Int =
implicitly[Numeric[A]].toDouble(first) compare implicitly[Numeric[B]].toDouble(second)
scala> compare(0.0,1.0)
res30: Int = -1
scala> compare(0.0,1)
res31: Int = -1
scala> compare(0,1.0)
res32: Int = -1
scala> compare(0,1)
res33: Int = -1
精彩评论