Home > Java, programming, scala > Scala – type inferencing gotchas

Scala – type inferencing gotchas


I’ve written previously about Scala; today I’m going to write about a potential ‘gotcha’ of Scala’s type inferencing.  Type inferencing means that instead of writing
int x = 100;

as we would in Java, you can instead write

val x = 100
and the compiler is smart enough to figure out that you mean an integer.  Let’s look at one case in which the use of implicit typing came back to bite me.

The setup

I was writing a routine that calculates the ‘average’ color of an image (where we define the average color as the color formed by the average red, blue, and green components of all the pixels in the image).  A first pass might look like this:
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)
}
(I’m leaving off the actual summation algorithm, as it’s not important to illustrate how the problem manifests itself.)
This code is almost right, but it has a problem.  Can you guess what it is?

Problem #1

In a lot of cases this code will work fine.  But if you have an extremely high resolution image, or you have an overexposed image (where many of the pixels are near white, implying a high red, green, and blue value), any of the individual sums can overflow, if the sum exceeds the max value that an integer can hold.  Due to the way binary numbers are implemented (“Twos complement”), this results in a negative number.
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.

The easiest way to fix this is to use a bigger datatype to store the running sum; let’s use a Long instead.  This allows us a maximum value of 9223372036854775807; that should be plenty big to store whatever running total we have.
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)
}
This code looks OK at a cursory glance; if you actual use it, you will quickly get an exception:
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.

This wouldn’t be a problem, except that there is no Color constructor that takes in Long arguments.  Yet the code compiles fine.  Why?
Well, it’s because of the implicit widening conversions present in the Java language.  These (usually) make our lives as programmers much easier.  If a method is defined to take in an integer and we pass it a short instead, the short is implicitly converted to an integer with no extra effort on our part.  This is done automatically because there is no danger of truncation; because the type that is implicitly converted to is larger (wider), it can always hold the value of the smaller type.
According to the Sun specification,


The following 19 specific conversions on primitive types are called the widening primitive conversions:

     

  • byte to shortintlongfloat, or double
  • short to intlongfloat, or double
  • char to intlongfloat, or double
  • int to longfloat, or double
  • long to float or double
  • float to double

The implicit conversion that tripped me up was that longs are implicitly promoted to float.  The reason the method compiled, even though there is no constructor that takes long r,g,b arguments, is because there is a constructor that takes float arguments, and the longs were automatically converted to floats!  See the Color documentation for more. Unlike the integer constructor, which takes RGB values in the range [0,255], the float constructor takes RGB values in the range [0,1].
The solution is to convert the longs back into ints, via a cast.  In Java this would be accomplished with
long redSum = ...;
int averageRed = (int) (redSum/numPixels);
In Scala you instead must do
val redSum:Long = ...
val averageRed:Int = (redSum/numPixels).asInstanceOf[Int]
The fixed code as a whole thus looks like
	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.

Advertisements
  1. Dan
    January 25, 2011 at 2:15 pm

    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)

    • Dan
      January 25, 2011 at 2:23 pm

      whoops, that last line should have been:
      new Color(red.toInt, green.toInt, blue.toInt)

      • i82much
        January 25, 2011 at 3:47 pm

        Oh cool, I didn’t know about the toInt. That’s a little less verbose than asInstanceOf[Int]! Thanks

  2. steve
    January 26, 2011 at 11:39 am

    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.

    • i82much
      January 26, 2011 at 12:09 pm

      @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.

  1. January 28, 2013 at 7:43 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: