Wouldn't it be nice if you could have both a compact and efficient solution? It turns out that you can, given Scala's @specialized
feature. First a warning: the feature is somewhat buggy, and may break if you try to use it for something too complicated. But for this case, it's almost perfect.
The @specialized
annotation creates separate classes and/or methods for each primitive type, and then calls that instead of the generic version whenever the compiler knows for sure what the primitive type is. The only drawback is that it does all of this completely automatically--you don't get to fill in your own method. That's kind of a shame, but you can overcome the problem using type classes.
Let's look at some code:
import java.nio.ByteBuffer
trait BufferWriter[@specialized(Byte,Int) A]{
def write(b: ByteBuffer, a: A): Unit
}
class ByteWriter extends BufferWriter[Byte] {
def write(b: ByteBuffer, a: Byte) { b.put(a) }
}
class IntWriter extends BufferWriter[Int] {
def write(b: ByteBuffer, a: Int) { b.putInt(a) }
}
object BufferWriters {
implicit val byteWriter = new ByteWriter
implicit val intWriter = new IntWriter
}
This gives us a BufferWriter
trait which is generic, but we override each of the specific primitive types that we want (in this case Byte
and Int
) with an appropriate implementation. Specialization is smart enough to link up this explicit version with the hidden one it normally uses for specialization. So you've got your custom code, but how do you use it? This is where the implicit vals come in (I've done it this way for speed and clarity):
import BufferWriters._
def write[@specialized(Byte,Int) A: BufferWriter](b: ByteBuffer, ar: Array[A]) {
val writer = implicitly[BufferWriter[A]]
var i = 0
while (i < ar.length) {
writer.write(b, ar(i))
i += 1
}
}
The A: BufferWriter
notation means that in order to call this write
method, you need to have an implicit BufferWriter[A]
handy. We've supplied them with the vals in BufferWriters
, so we should be set. Let's see if this works.
val b = ByteBuffer.allocate(6)
write(b, Array[Byte](1,2))
write(b, Array[Int](0x03040506))
scala> b.array
res3: Array[Byte] = Array(1, 2, 3, 4, 5, 6)
If you put these things in a file and start poking around the classes with javap -c -private
you'll see that the appropriate primitive methods are being used.
(Note that if you didn't use specialization, this strategy would still work, but it would have to box values inside the loop to copy the array out.)