【问题标题】:Scala reduceLeft: 0.asInstanceOf[B] [duplicate]Scala reduceLeft:0.asInstanceOf[B] [重复]
【发布时间】:2021-07-03 10:50:55
【问题描述】:

继续my series关于奇怪的源代码。

查看 Scala 2.12.12 scala.collection.TraversableOnce#reduceLeft#reducer 我发现了很奇怪的一行:

def reduceLeft[B >: A](op: (B, A) => B): B = {
  if (isEmpty)
    throw new UnsupportedOperationException("empty.reduceLeft")

  object reducer extends Function1[A, Unit] {
    var first = true
    var acc: B = 0.asInstanceOf[B] // <<<<===

    override def apply(x: A): Unit =
      if (first) {
        acc = x
        first = false
      }
      else acc = op(acc, x)
  }
  self foreach reducer
  reducer.acc
}

0.asInstanceOf[B] 实际上是做什么的?这是使每种类型“可空”的解决方法吗?

例如,拥有

Seq("1", "2").reduceLeft(_ + _)

表示运行时中的以下代码

var acc: B = 0.asInstanceOf[String]

为什么不能简单地用var acc: B = null 代替?因为它需要介绍implicit ev: Null &lt;:&lt; A1或什么?

更新:

此外,简单地将Int 转换为任何其他类型都会引发异常:

println(0.asInstanceOf[String])

抛出运行时异常:

Exception in thread "main" java.lang.ClassCastException: 
    java.lang.Integer cannot be cast to java.lang.String

但是为什么在使用reducer的情况下它不会抛出异常?

更新 2:

潜水更深,

def foo[A]: A = 1.asInstanceOf[A]

println(foo[String])                 // 1
println(foo[LocalDateTime])          // 1
println(foo[LocalDateTime].getClass) // java.lang.Integer

Source code

【问题讨论】:

  • 是的,这似乎是一种欺骗编译器使其具有空值的方法。
  • @LuisMiguelMejíaSuárez 是的。但是行为是如此奇怪......我在问题中添加了几个例子来证明它的行为有多荒谬:D
  • 坏演员有点像一棵树,倒在森林里:只有当你它时它才会抛出。
  • 如果B 是原语,var acc: B = null 将不起作用。

标签: scala null scala-collections


【解决方案1】:

这是部分答案。

您确实不能将null 分配给未指定为B &lt;: AnyRefB 类型的值而不进行强制转换。但是,Scala 泛型在运行时会被删除。删除后的装箱代码如下所示:

def reduceLeft(op: Function2): Object = {
  if (isEmpty)
    throw new UnsupportedOperationException("empty.reduceLeft")

  object reducer extends Function1 {
    var first = true
    var acc: Object = BoxesRunTime.boxToInteger(0)

    override def apply(x: Object): Unit =
      if (first) {
        acc = x
        first = false
      }
      else acc = op(acc, x)
  }
  self foreach reducer
  reducer.acc
}

请注意,演员也消失了。关于B 是什么的信息为零,因此无需检查。

与那里不同:

println(0.asInstanceOf[String])

此转换不会被删除,因为 String 是已知类型。

擦除还解释了为什么 foo 调用有效,因为 foo 的擦除基本上是:

def foo: Object = BoxesRunTime.boxToInteger(1)

回想一下,println 被定义为 def println(any: Any): Unit,而后 scalac 的 AnyObject 替换。因此,当你在做

println(foo[String])                 // 1
println(foo[LocalDateTime])          // 1

生成的1 永远不会分配给任何会进行运行时类检查的东西。取出来的对象直接传给println

但这会导致 ClassCastException,因为 foo 的结果需要转换为 String 以进行 printString 调用

def printString(s: String) = println(s)
printString(foo[String])

这也会在运行时失败:

val str = foo[String]

因为 Scala 会将 str 的类型推断为 String,然后运行时将无法转换为该类型。


现在,我没有的部分答案是为什么他们不做var acc: B = _(只在类/对象中有效)或var acc: B = null.asInstanceOf[B](在任何地方都有效)可以定义一个var)。它可能只是通过一堆重构而幸存下来的一行。

【讨论】:

  • 我喜欢你个人资料中的 Learning Scala when daily brainpower isn't all used up. ?
【解决方案2】:

在 Scala 2.13 中,0.asInstanceOf[B]Expression for all zero bits #8767 更改为 null.asInstanceOf[B]

null.asInstanceOf[A] 比 0 更洁净。

这里无关紧要,因为该值从未使用过,并且始终 重新分配。

太糟糕了,有人开始指责var x: A = _ 过度丑陋。 这仍然是不赋值的最佳表达方式。

Scala 3我们可以写

trait Foo[B]:                                                                                                                                        
  var acc: B = compiletime.uninitialized

哪个 IMO 清楚地传达了意图。


相关What is happening with 0.asInstanceOf[B] in Scala reduceLeft implementation

【讨论】:

  • 天哪,这已经被回答了!我没能找到它。将其作为副本关闭。并感谢您指出 PR。
猜你喜欢
  • 2012-01-17
  • 2018-06-27
  • 2015-03-20
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多