Как получить значение по умолчанию для типа в Scala?
Я пытаюсь написать функцию Scala, которая возвращает значение по умолчанию для типа (0, 0.0, false, '\ 0' и т.д. для типов значений и null для ссылочных типов). Я придумал это:
def defaultValue[U]: U = {
class Default[U] { var default: U = _ }
new Default[U].default
}
и хотя это хорошо работает, если вызвано напрямую, оно возвращает null даже для типов значений при вызове через функцию, которая сама по себе является общей, как показано в этом сеансе REPL:
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.
Может кто-нибудь объяснить мне:
- почему это происходит (и почему компилятор/интерпретатор не жалуется, если нет достаточной информации для его возврата true Boolean); и
- Как я могу это исправить?
Ответы
Ответ 1
Вы можете создать свой собственный Default
type-class, чтобы справиться с этим. Вот как выглядит код. Я добавил специальную обработку для коллекций scala, которая возвращает пустую коллекцию вместо нулевого.
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
}
Это результаты использования этого в repl. Обратите внимание, что значение по умолчанию для String
можно переопределить, создав новый неявный 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] = [email protected]
scala> Default.value[String]
res7: String = ""
Ответ 2
Вот более сжатая версия вашей проблемы:
scala> defaultValue[Boolean]: Any
res0: Any = null
scala> defaultValue[Boolean]: Boolean
res1: Boolean = false
Первая версия - это то, что применяется, когда вы вызываете res = defaultValue[U]
, потому что хотя U
имеет тип Boolean, res
имеет тип Any
Если вы скомпилируете эту небольшую программу, используя опцию -Xprint:all
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)
}
}
Вы увидите, что перед фазой стирания у вас есть:
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)
Затем в конце фазы стирания:
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))
Итак, что происходит, так это то, что под капотом defaultValue[Boolean]
возвращает null в обоих случаях, но затем null становится unboxed в false, когда возвращаемый тип является логическим. Вы можете проверить, что в REPL:
scala> Boolean.unbox(null)
res0: Boolean = false
scala> null.asInstanceOf[Boolean]
res1: Boolean = false
Edit: У меня была идея - не то, что я рекомендую. Не уверен, что ваш вариант использования (res = false
мне кажется проще).
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
Ответ 3
Для записи здесь единственное, что я нашел (еще), чтобы сделать эту работу надежно. Усовершенствования приветствуются.
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]
}
Я знаю, что я получаю null, даже если T <: NotNull
, что является проблемой. И снова возникает проблема с инициализацией vars с _
для подклассов NotNull
.
Ответ 4
Я знаю, что уже есть "лучший ответ", но как насчет действительно простого:
def defaultValue[U: ClassManifest]: U = new Array[U](1)(0)
Кажется, что он работает, хотя и несколько дорогой из-за создания временного объекта массива. Кто-нибудь знает о любых случаях, когда он дает неправильное значение?
Я искал "более дешевую" альтернативу, но этот вопрос говорит мне, что, вероятно, не один.
Ответ 5
Я написал сообщение в блоге о создании механизма по умолчанию для Scala. Вы можете найти здесь здесь.
Если вы не хотите, чтобы Option[_]
по умолчанию был None
, String
до ""
и т.д., тогда избавиться от соответствующих имплицитов от объекта Default
.