如何获取 Scala 中类型的默认值?

发布于 2024-10-21 08:53:59 字数 1038 浏览 4 评论 0原文

我正在尝试编写一个 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.

有人可以向我解释一下:

  1. 为什么会发生这种情况(以及为什么编译器/解释器在没有足够的信息返回真布尔值时不会抱怨); 我该如何
  2. 解决它?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

谈场末日恋爱 2024-10-28 08:53:59

这是您的问题的更精简版本:

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

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

第一个版本是您调用 res = defaultValue[U] 时应用的版本,因为即使 U 是布尔类型,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,但当返回类型为 Boolean 时,null 会被拆箱为 false。您可以在 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

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
温暖的光 2024-10-28 08:53:59

您可以创建自己的默认 type-class 来处理这个问题。代码如下所示。我为 scala 集合添加了特殊处理,返回空集合而不是 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
}

这些是在 repl 中使用它的结果。请注意,可以通过创建新的隐式 Default[String] 来覆盖 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 = ""

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 = ""
纸短情长 2024-10-28 08:53:59

作为记录,这是我发现的唯一可以使这项工作可靠的方法。欢迎改进。

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 我也会得到 null,这是一个问题。然而,对于 NotNull 子类来说,使用 _ 初始化变量存在问题。

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.

七色彩虹 2024-10-28 08:53:59

我知道已经有了“最佳答案”,但是真正简单的呢:

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

它似乎有效,尽管由于创建临时数组对象而有点昂贵。有谁知道它给出错误值的情况吗?

我一直在寻找一种“更便宜”的替代方案,但这个问题告诉我可能没有。

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.

只有一腔孤勇 2024-10-28 08:53:59

我写了一篇关于为 Scala 构建默认机制的博客文章。您可以在此处找到它。

如果您不希望 Option[_] 默认为 NoneString"" 等,则摆脱来自对象 Default 的相应隐式。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文