I would like to write a SparseVector[T]
class where T
can be a double, an int or a boolean.
The class will not be backed by an array (because I want a sparse data structure) but I have seen that when I build an empty array of an AnyVal
type, the elements are initialized to the default value. For instance:
scala> new Array[Int](10)
res0: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> new Array[Boolean](10)
res1: Array[Boolean] = Array(false, false, false, false, false, false, false, false, false, false)
scala> new Array[Double](10)
res2: Array[Double] = Array(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
How can I include this default value in my class ? The behaviour I would like to get is:
val v = new SparseVector[Double](100)
println( v(12) ) // should print '0.0'
val w = new SparseVector[Boolean](100)
println( v(85) ) // should print 'false'
Thanks
A default value expression produces the default value of a type. There are two kinds of default value expressions: the default operator call and a default literal. You also use the default keyword as the default case label within a switch statement.
If T is a class or interface type, default(T) is the null reference. If T is a struct, then default(T) is the value where all the bits are zero.
You could leverage the fact that Scala already provides you a way of getting the default value of a type. When you write var x: Int = _
, this initialises x
to 0
. Similar for all AnyVal
types. All AnyRef
types are initialised to null
.
Bearing that in mind, you could rewrite your sparse vector class as following:
class SparseVector[T](val size: Int) {
import scala.collection.mutable.Map
private var default: T = _
private[this] val storage = Map[Int, T]()
def apply(key: Int) =
if(key < size)
storage.getOrElse(key, default)
else
throw new IllegalArgumentException("Index " + key + " out of bounds")
def update(key: Int, value: T) { storage(key) = value }
}
Now code like the following works as expected:
scala> val b = new SparseVector[Boolean](10)
b: SparseVector[Boolean] = SparseVector@cfd22a
scala> b(1)
res20: Boolean = false
scala> b(1) = true
scala> b(1)
res22: Boolean = true
scala> val i = new SparseVector[Int](10)
i: SparseVector[Int] = SparseVector@1813c12
scala> i(1)
res23: Int = 0
scala> i(1) = 10
scala> i(1)
res25: Int = 10
scala> i(10)
java.lang.IllegalArgumentException: Index 10 out of bounds
A couple of improvements I might make to this class:
object SparseVector {
def apply[T](size: Int) = new SparseVector[T](size)
def apply[T](size: Int, default: T) = {
val result = new SparseVector[T](size)
result.default = default
result
}
}
Now this works:
scala> val b = SparseVector[Boolean](10, true)
b: SparseVector[Boolean] = SparseVector@126f29f
scala> b(4)
res28: Boolean = true
scala> val i = SparseVector[Int](10, 42)
i: SparseVector[Int] = SparseVector@b9979b
scala> i(3)
res30: Int = 42
EDIT: The code I have written works with Scala 2.7.6.final. Mitch Blevins has pointed out that the code yields null
as a default value for AnyVal
types when run with Scala 2.8r.19890. As explained in the comments, this should not be possible as Null
is not a subtype of AnyVal
.The general idea should be similar if using 2.8, as var b: Boolean = _
should still give you the default value of the Boolean
type. The usage of collections to store the sparse vector might be different, but as I said in the comment, I am not familiar with the 2.8 collection redesign.
EDIT2: ... the null
behaviour should not be possible, but unfortunately it is. Doing some more research into the problem it seems that due to type erasure the field default
always gets initialised to null
. And after that... weirdness ensues. See Mitch's post for a discussion and some bear-bones code reproducing the problem.
Things I have tried and failed in order to make the code work as it should:
null.asInstanceOf[T]
- nope, Java doesn't have reified generics. This still yields null
@specialised
- nope, it seems that even if the compiler generates specialised code for primitives, you still get the null behaviourAnyVal
, which should not be null
. Nope. Still null
.So conceptually, my solution should work. But it doesn't due to very strange behaviour which I have reported in the Scala Trac.
See also this blog post for a nice discussion of null
able AnyVal
s.
-- Flaviu Cipcigan
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With