Scala – type inferencing gotchas
int x = 100;
as we would in Java, you can instead write
val x = 100
The setup
def calculateAverageColor(image:BufferedImage):Color = { var redSum = 0 var greenSum = 0 var blueSum = 0 // calculate the sum of each channel here val red = (redSum / numPixels) val green = (greenSum / numPixels) val blue = (blueSum / numPixels) new Color(red, green, blue) }
Problem #1
scala> java.lang.Integer.MAX_VALUE res0: Int = 2147483647 scala> java.lang.Integer.MAX_VALUE+1 res2: Int = -2147483648
If this happens, then we end up trying to construct a color with negative red, green, or blue values; this will result in an IllegalArgumentException.
def calculateAverageColor(image:BufferedImage):Color = { // Declare the sums as longs so we don't have to worry about overflow var redSum:Long = 0 var greenSum:Long = 0 var blueSum:Long = 0 // calculate the sum of each channel val red = (redSum / numPixels) val green = (greenSum / numPixels) val blue = (blueSum / numPixels) new Color(red, green, blue) }
java.lang.IllegalArgumentException: Color parameter outside of expected range: Red, Green, Blue
Problem #2
At this point, you might start debugging the process by printing out the values of red, green, and blue. Sure enough they’ll be in the range [0, 255], just as you need for the Color constructor. What is going on?
There are two related problems. The first is that the type of red, green, and blue are not integers, due to the Long value in the computation. The compiler sees the Long and (correctly) infers that the type of red, green, and blue must be Long.
”
The following 19 specific conversions on primitive types are called the widening primitive conversions:
- byte to short, int, long, float, or double
- short to int, long, float, or double
- char to int, long, float, or double
- int to long, float, or double
- long to float or double
- float to double
”
long redSum = ...; int averageRed = (int) (redSum/numPixels);
val redSum:Long = ... val averageRed:Int = (redSum/numPixels).asInstanceOf[Int]
var redSum:Long = 0 var greenSum:Long = 0 var blueSum:Long = 0 // calculate the sum of each channel val red:Int = (redSum / numPixels).asInstanceOf[Int] val green:Int = (greenSum / numPixels).asInstanceOf[Int] val blue:Int = (blueSum / numPixels).asInstanceOf[Int] new Color(red, green, blue)
Conclusion
One of the nice things about Scala is that you do not need to explicitly declare the types of your variables. In one sequence of unfortunate events, the variables that looked like ints were in fact longs, leading to an implicit conversion to the float primitive type, which in turn caused the incorrect constructor to be invoked, and an IllegalArgumentException. Hopefully you can avoid doing something so foolish as a result of reading this post.
I generally prefer the following style for implying the types in this sort of scenario because it is shorter, but I’m sure many others prefer the more explicit form:
var redSum = 0L
var greenSum = 0L
var blueSum = 0L
// calculate the sum of each channel
val red = (redSum / numPixels)
val green = (greenSum / numPixels)
val blue = (blueSum / numPixels)
new Color(red:Int, green:Int, blue:Int)
whoops, that last line should have been:
new Color(red.toInt, green.toInt, blue.toInt)
Oh cool, I didn’t know about the toInt. That’s a little less verbose than asInstanceOf[Int]! Thanks
I fail to see how this is a type inferencing gotcha.
> In one sequence of unfortunate events, the variables that looked like ints were in fact longs, …
Where?
> leading to an implicit conversion to the float primitive type, which in turn caused the incorrect constructor to be invoked, and an IllegalArgumentException.
Right. The conversion from integer numbers to floating point is a design mistake of Java. Sadly, Scala tries to be compatible with Java here.
@steve – when I say they ‘looked like ints’ I mean from the purposes of printing out the values in a debugging session. With a little thought and care I should have noticed that they were Long values.
I understand that the long->float conversion is a Java design issue, not a Scala, but the fact that you don’t need to explicitly declare the types of your variables in Scala can make this issue bite you more easily.
It’s nothing insurmountable, but I was very surprised that longs were ‘promoted’ to floats.