Scala reduceLeft: 0.asInstanceOf[B]
Scala reduceLeft: 0.asInstanceOf[B]
继续关于奇怪的源代码。
查看 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(_ + _)
表示runtime中有如下代码
var acc: B = 0.asInstanceOf[String]
为什么不能简单地用var acc: B = null
代替呢?因为它需要引入 implicit ev: Null <:< A1
或者什么?
更新:
此外,简单地将 Int
转换为任何其他类型都会引发异常:
println(0.asInstanceOf[String])
抛出运行时异常:
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
但是为什么它在使用 reducer 的情况下不抛出异常?
更新二:
深入研究,
def foo[A]: A = 1.asInstanceOf[A]
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
println(foo[LocalDateTime].getClass) // java.lang.Integer
这是部分答案。
您确实不能将 null
分配给未指定为 B <: AnyRef
的类型 B
的值,而无需强制转换。但是,Scala 泛型在运行时被删除。 post-erasure post-boxing 代码看起来像这样:
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
,post-scalac Any
被 Object
替换。因此,当你在做
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
结果 1
永远不会分配给任何会进行运行时 class 检查的东西。出来的对象直接传给println
.
但这会导致 ClassCastException,因为 foo
的结果需要转换为 String
以便 printString
调用
def printString(s: String) = println(s)
printString(foo[String])
这也会在运行时失败:
val str = foo[String]
因为 Scala 会将 str
的类型推断为 String
,然后运行时将无法转换为该类型。
现在,我 没有 的答案的一部分是为什么他们没有 var acc: B = _
(只在 classes/objects 内部有效)或 var acc: B = null.asInstanceOf[B]
(在任何可以定义 var
的地方都有效)。它可能只是通过一系列重构幸存下来的一行。
在 Scala 2.13 中,0.asInstanceOf[B]
被 Expression for all zero bits #8767
更改为 null.asInstanceOf[B]
null.asInstanceOf[A]
is more kosher than 0.
It doesn't matter here because the value is never used and is always
re-assigned.
It's too bad someone started accusing var x: A = _
of undue ugliness.
That remains the best expression of non-assignment.
在Scala 3中我们可以写
trait Foo[B]:
var acc: B = compiletime.uninitialized
IMO 明确表达了意图。
相关What is happening with 0.asInstanceOf[B] in Scala reduceLeft implementation
继续
查看 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(_ + _)
表示runtime中有如下代码
var acc: B = 0.asInstanceOf[String]
为什么不能简单地用var acc: B = null
代替呢?因为它需要引入 implicit ev: Null <:< A1
或者什么?
更新:
此外,简单地将 Int
转换为任何其他类型都会引发异常:
println(0.asInstanceOf[String])
抛出运行时异常:
Exception in thread "main" java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String
但是为什么它在使用 reducer 的情况下不抛出异常?
更新二:
深入研究,
def foo[A]: A = 1.asInstanceOf[A]
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
println(foo[LocalDateTime].getClass) // java.lang.Integer
这是部分答案。
您确实不能将 null
分配给未指定为 B <: AnyRef
的类型 B
的值,而无需强制转换。但是,Scala 泛型在运行时被删除。 post-erasure post-boxing 代码看起来像这样:
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
,post-scalac Any
被 Object
替换。因此,当你在做
println(foo[String]) // 1
println(foo[LocalDateTime]) // 1
结果 1
永远不会分配给任何会进行运行时 class 检查的东西。出来的对象直接传给println
.
但这会导致 ClassCastException,因为 foo
的结果需要转换为 String
以便 printString
调用
def printString(s: String) = println(s)
printString(foo[String])
这也会在运行时失败:
val str = foo[String]
因为 Scala 会将 str
的类型推断为 String
,然后运行时将无法转换为该类型。
现在,我 没有 的答案的一部分是为什么他们没有 var acc: B = _
(只在 classes/objects 内部有效)或 var acc: B = null.asInstanceOf[B]
(在任何可以定义 var
的地方都有效)。它可能只是通过一系列重构幸存下来的一行。
在 Scala 2.13 中,0.asInstanceOf[B]
被 Expression for all zero bits #8767
null.asInstanceOf[B]
null.asInstanceOf[A]
is more kosher than 0.It doesn't matter here because the value is never used and is always re-assigned.
It's too bad someone started accusing
var x: A = _
of undue ugliness. That remains the best expression of non-assignment.
在Scala 3中我们可以写
trait Foo[B]:
var acc: B = compiletime.uninitialized
IMO 明确表达了意图。
相关What is happening with 0.asInstanceOf[B] in Scala reduceLeft implementation