Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default value for generic data structure

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

like image 519
paradigmatic Avatar asked Dec 04 '09 18:12

paradigmatic


People also ask

What is default() in c#?

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.

What is the default value for type T?

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.


1 Answers

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:

  • Have a `toString` method printing the collection in a reasonable way
  • Provide a companion object which can change the default value of the vector if required (see the code below).
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 behaviour
  • Casting the result to an AnyVal, 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 nullable AnyVals.

-- Flaviu Cipcigan

like image 95
Flaviu Cipcigan Avatar answered Nov 11 '22 19:11

Flaviu Cipcigan