How can I obtain the default value for a type in Scala?

12,739

Solution 1

You can create your own Default type-class to handle this. Here is what the code looks like. I added special handling for scala collections that returns an empty collection instead of null.

import scala.collection.immutable

class Default[+A](val default: A)

trait LowerPriorityImplicits {
  // Stop AnyRefs from clashing with AnyVals
  implicit def defaultNull[A <: AnyRef]:Default[A] = new Default[A](null.asInstanceOf[A])  
}

object Default extends LowerPriorityImplicits {
  implicit object DefaultDouble extends Default[Double](0.0)
  implicit object DefaultFloat extends Default[Float](0.0F)
  implicit object DefaultInt extends Default[Int](0)
  implicit object DefaultLong extends Default[Long](0L)
  implicit object DefaultShort extends Default[Short](0)
  implicit object DefaultByte extends Default[Byte](0)
  implicit object DefaultChar extends Default[Char]('\u0000')
  implicit object DefaultBoolean extends Default[Boolean](false)
  implicit object DefaultUnit extends Default[Unit](())

  implicit def defaultSeq[A]: Default[immutable.Seq[A]] = new Default[immutable.Seq[A]](immutable.Seq())
  implicit def defaultSet[A]: Default[Set[A]] = new Default[Set[A]](Set())
  implicit def defaultMap[A, B]: Default[Map[A, B]] = new Default[Map[A, B]](Map[A, B]())
  implicit def defaultOption[A]: Default[Option[A]] = new Default[Option[A]](None)

  def value[A](implicit value: Default[A]): A = value.default
}

These are the results of using this in the repl. Notice that the default value for String can be overriden by creating a new implicit Default[String].

scala> Default.value[Int]
res0: Int = 0

scala> Default.value[Boolean]
res1: Boolean = false

scala> Default.value[String]
res2: String = null

scala> Default.value[Set[Int]]
res3: Set[Int] = Set()

scala> Default.value[immutable.Seq[Int]]
res4: scala.collection.immutable.Seq[Int] = List()

scala> Default.value[String]
res5: String = null

scala> Default.value[AnyRef]
res6: AnyRef = null

scala> implicit val emptyStringAsDefault:Default[String] = new Default[String]("")
emptyStringAsDefault: Default[String] = Default@7d78d7b4

scala> Default.value[String]
res7: String = ""

Solution 2

Here is a more condensed version of your issue:

scala> defaultValue[Boolean]: Any
res0: Any = null

scala> defaultValue[Boolean]: Boolean
res1: Boolean = false

The first version is what applies when you call res = defaultValue[U] because even though U is of type Boolean, res is of type Any

If you compile this little program using the -Xprint:all option

object Test {
  def defaultValue[U]: U = { class Default[U] {var default: U = _ }; new Default[U].default }

  def main(args:Array[String]) {
    val any = defaultValue[Boolean]: Any
    println(any)
    val bool = defaultValue[Boolean]: Boolean
    println(bool)
  }
}

You'll see that right before the erasure phase, you have:

val any: Any = (Test.this.defaultValue[Boolean](): Any);
scala.this.Predef.println(any);
val bool: Boolean = (Test.this.defaultValue[Boolean](): Boolean);
scala.this.Predef.println(bool)

Then at the end of the erasure phase:

val any: java.lang.Object = (Test.this.defaultValue(): java.lang.Object);
scala.this.Predef.println(any);
val bool: Boolean = (scala.Boolean.unbox(Test.this.defaultValue()): Boolean);
scala.this.Predef.println(scala.Boolean.box(bool))

So what happens is that under the hood defaultValue[Boolean] returns null in both cases, but then null is unboxed into false when the return type is a Boolean. You can verify that in the REPL:

scala> Boolean.unbox(null)
res0: Boolean = false

scala> null.asInstanceOf[Boolean]
res1: Boolean = false

Edit: I had an idea - not that I'm recommending it. Not sure what your use case is (res = false seems easier to me..)

scala> def f[@specialized U] = { class X { var x: U = _ }; (new X).x }
f: [U]U

scala> var res: Any = _
res: Any = null

scala> def g[@specialized U] = { res = f[U]; f[U] }
g: [U]U

scala> g[Boolean]
res0: Boolean = false

scala> res
res1: Any = false

Solution 3

I know there is already "best answer", but what about the really simple:

def defaultValue[U: ClassManifest]: U = new Array[U](1)(0)

It seems to work, although it's somewhat expensive due to creating a temporary array object. Does anyone know of any cases where it gives the wrong value?

I was looking for a "cheaper" alternative, but this question tells me there probably isn't one.

Solution 4

For the record, here's the only I've found (yet) to make this work reliably. Improvements are welcome.

def defaultValue[T: ClassManifest]: T = classManifest[T].erasure.toString match {
  case "void" => ().asInstanceOf[T]
  case "boolean" => false.asInstanceOf[T]
  case "byte" => (0: Byte).asInstanceOf[T]
  case "short" => (0: Short).asInstanceOf[T]
  case "char" => '\0'.asInstanceOf[T]
  case "int" => 0.asInstanceOf[T]
  case "long" => 0L.asInstanceOf[T]
  case "float" => 0.0F.asInstanceOf[T]
  case "double" => 0.0.asInstanceOf[T]
  case _ => null.asInstanceOf[T]
}

I'm aware that I get null even if T <: NotNull, which is a problem. Then again, there is a problem with initialization of vars with _ for NotNull subclasses.

Solution 5

I have written a blog post on building a defaulting mechanism for Scala. You can find it here.

If you do not want Option[_] to default to None, String to "" etc then get rid of the respective implicits from the object Default.

Share:
12,739
Jean-Philippe Pellet
Author by

Jean-Philippe Pellet

Lecturer (EPFL) &amp; developer (University of Teacher Education, Lausanne, Switzerland). Ph.D. in computer science. Interests include: Language design (big fan of Scala) Machine learning, statistics, statistical causality Pedagogical tools Teaching of computer science and computational thinking You can contact me at jppellet at gmail dot com.

Updated on June 05, 2022

Comments

  • Jean-Philippe Pellet
    Jean-Philippe Pellet almost 2 years

    I'm trying to write a Scala function that returns the default value of a type (0, 0.0, false, '\0', etc. for value types and null for reference types). I came up with this:

    def defaultValue[U]: U = {
      class Default[U] { var default: U = _ }
      new Default[U].default
    }
    

    and while this works well if called directly, it returns null even for value types when called through a function that itself is generic, as shown in this REPL session:

    Welcome to Scala version 2.8.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_24).
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> def defaultValue[U]: U = { class Default[U] {var default: U = _ }; new Default[U].default }
    defaultValue: [U]U
    
    scala> defaultValue[Boolean] // direct call works
    res0: Boolean = false
    
    scala> var res: Any = 0
    res: Any = 0
    
    scala> def setRes[U] = { res = defaultValue[U]; defaultValue[U] }
    setRes: [U]U
    
    scala> setRes[Boolean] // returns a Boolean, but...
    res1: Boolean = false
    
    scala> res
    res2: Any = null // ... sets the res variable to null.
    

    Can someone explain to me:

    1. why this happens (and why the compiler/interpreter doesn't complain if there is not enough information for it to return a true Boolean); and
    2. how I can fix it?
  • Jean-Philippe Pellet
    Jean-Philippe Pellet about 13 years
    Thanks, that's a very nice explanation for the Why. Now why can't the compiler tell me more at compile time? Like, look man, you want a Boolean, but here I may give you null, sorry?
  • huynhjl
    huynhjl about 13 years
    Not sure there are other use cases of def f[T]: T other than the one you have. It's hard to materialize a T without any passed parameter. So may be there is no warning because nobody ever thought of doing that.
  • Jean-Philippe Pellet
    Jean-Philippe Pellet about 13 years
    The @specialized trick is nice, but dangerous because it breaks down as soon as you wrap the call to f from a context which is not specialized. This could cause bugs that are hard to track down.
  • Puterdo Borato
    Puterdo Borato about 4 years
    So simply calling null.asInstanceOf[B] should perfectly do the job.