Как я могу получить значение по умолчанию для типа в Scala? - PullRequest
15 голосов
/ 10 марта 2011

Я пытаюсь написать функцию Scala, которая возвращает значение по умолчанию для типа (0, 0.0, false, '\ 0' и т. Д. Для типов значений и ноль для ссылочных типов).Я придумал это:

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

, и хотя это работает хорошо, если вызывается напрямую, он возвращает ноль даже для типов значений при вызове через функцию, которая сама является универсальной, как показано в этом сеансе 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.

Может ли кто-нибудь объяснить мне:

  1. , почему это происходит (и почему компилятор / интерпретатор не жалуется, если ему недостаточно информации для возврата истинного логического значения);и
  2. как я могу это исправить?

Ответы [ 5 ]

8 голосов
/ 11 марта 2011

Вот более сжатая версия вашей проблемы:

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

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

Первая версия применяется при вызове res = defaultValue[U], поскольку, хотя U имеет тип Boolean, res имеетвведите Любой

Если вы скомпилируете эту маленькую программу, используя опцию -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] возвращает ноль в обоих случаях, но затем ноль распаковывается в ложькогда тип возвращаемого значения является логическим.Вы можете проверить это в REPL:

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

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

Редактировать: у меня была идея - не то, чтобы я ее рекомендовал.Не знаю, какой у вас вариант использования (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
7 голосов
/ 14 марта 2011

Для справки, вот единственное, что я нашел (пока), чтобы сделать эту работу надежно. Улучшения приветствуются.

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]
}

Я знаю, что я получаю нулевое значение, даже если T <: NotNull, что является проблемой. Опять же, существует проблема с инициализацией переменных с _ для NotNull подклассов.

6 голосов
/ 13 июня 2014

Вы можете создать свой собственный Default тип-класса , чтобы справиться с этим.Вот как выглядит код.Я добавил специальную обработку для коллекций 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] = Default@7d78d7b4

scala> Default.value[String]
res7: String = ""
.
6 голосов
/ 22 июня 2011

Я знаю, что уже есть «лучший ответ», но как насчет действительно простого:

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

Кажется, это работает, хотя это несколько дорого из-за создания временного объекта массива.Кто-нибудь знает какие-либо случаи, когда он дает неправильное значение?

Я искал "более дешевую" альтернативу, но этот вопрос говорит мне, что, вероятно, ее нет.

2 голосов
/ 18 августа 2011

Я написал сообщение в блоге о создании механизма по умолчанию для Scala. Вы можете найти его здесь .

Если вы не хотите, чтобы Option[_] по умолчанию равнялось None, String - "" и т. Д., Тогда избавьтесь от соответствующих последствий от объекта Default.

...