How can I obtain the default value for a type in Scala?
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
.
Jean-Philippe Pellet
Lecturer (EPFL) & 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, 2022Comments
-
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:
- 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
- how I can fix it?
-
Jean-Philippe Pellet about 13 yearsThanks, 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 about 13 yearsNot 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 about 13 yearsThe @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 about 4 yearsSo simply calling null.asInstanceOf[B] should perfectly do the job.