只要忽略返回值,将函数 [X, Y] X => Y 转换为 X => () 是否安全?
Is casting a function [X, Y] X => Y to X => () safe as long as I ignore the returned value?
val f = { x :Int => x }
val g = f.asInstanceOf[Int, Unit]
g(1) //works
上面的代码有多脆弱?它目前有效,但当然它不是语言规范的一部分。出于这个原因,当然有可能有一天它不会;我的问题是它的可能性有多大,从编译器的明智选择的意义上理解。我不太了解字节码,但我可以通过尝试将 Int
放入 Unit
寄存器来轻松想象它的中断。另一方面,这种工作实际上可能是文档规范的未记录结果
Scala 编译器和 Java 字节码本身。
我的动力主要在于 @specialized
代码。例如,Iterable
包含以下方法:
trait Iterable[E] /* ... */
def foreach[T](f :E => T) :Unit
可能选择此签名是为了允许传递 E
的任何函数作为参数。
但实际上,它几乎总是 lambda/method 值。上述方法的问题
是它需要专门研究 E
和 T
,导致方法变体的平方数,应该避免。不专注于 T
是没有意义的,因为 scalac 不会为类型参数的子集发出 @specialized
子类:如果任何 @specialized
参数接收到引用(或非专业)类型作为参数,整个对象将是已擦除超类的一个实例。同样,没有apply(Int):Object
方法——如果擦除return类型,那么会调用擦除的apply(Object):Object
,导致装箱。因此,在这种情况下更可取
def foreach(f :E => Unit) :Unit
引入这样的方法(使用不同的名称)并将其用作常规 foreach
的委托目标是可能的,并且不会导致任何装箱,至少只要创建的 lambda 不会没有引用类型作为其 return 类型。
视情况而定。
大多数情况下,Scala 会编译代码,以便返回 Unit
的函数将被调用为 JVM 的 void
(没有返回结果),或者它将其视为返回 scala.Unit
' s 唯一可以丢弃的值。只要 JVM 不会尝试对该返回值调用任何东西,它只会假设它是一些 java.lang.Object
如果用于任何事情,应该检查 scala.Unit
,这永远不会发生。
但这并不意味着不可能。
val f: Int => Int = _ * 2
val g: Unit => Int = _ => 10
(f.asInstanceOf[Int => Unit] andThen g)(10)
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class scala.runtime.BoxedUnit (java.lang.Integer is in module java.base of loader 'bootstrap'; scala.runtime.BoxedUnit is in unnamed module of loader 'app')
scala.Function1.$anonfun$andThen(Function1.scala:85)
scala.Function1.apply$mcII$sp(Function1.scala:69)
ammonite.$sess.cmd27$.<clinit>(cmd27.sc:1)
只要您在自己的代码中调用它,您拥有完全的控制权,始终了解上下文、测试等就应该没问题。
恕我直言,对于世界上基本上每个图书馆或任何将维护一周以上的代码库来说,这是一个错误的假设。
f.andThen(_ => ())
总是更安全,我必须有一些非常好的理由来使用像 .asInstanceOf
这样的优化,并在未来冒一些有趣的错误。
asInstanceOf
是一个本质上不安全的通用逃生舱口。当你使用它时,你实际上是在告诉类型检查器盲目地信任你。
一般来说,当您编写 x.asInstanceOf[Y]
时,您是在向编译器承诺值 x
确实属于 Y
.
类型
只有在 x
确实是 Y
类型的情况下才能保证有效。否则,如果 x
静态已知为基本类型或 null
,编译器将进行一些特殊处理。然后会发生以下两种情况之一:
- Runtime 会识破你的谎言并抛出
ClassCastException
.
- 你撒谎就逃脱了,可能还有其他事情打破了界限。
在你的例子中,f 的类型是 Int => Int
,这显然不是 Int => Unit
的子类型。换句话说:你在欺骗编译器,所有保证都是无效的。
这个故事的寓意是:你永远不应该依赖实现细节的正确性,你应该始终将 asInstanceOf
视为不安全的。
正如 Mateusz 所写,f.andThen(_ => ())
或等效的 { x => {f(x); ()}}
是将某些 A => B
转换为 A => Unit
的唯一正确方法。
您还应注意,虽然可以生成将专用函数作为参数的专用方法,但 (x => x).asInstanceOf[Int => Unit]
将始终 return 非专用 Function1[Int,Unit]
,而不是专用类型,所以这对你的性能完全没有帮助。
val f = { x :Int => x }
val g = f.asInstanceOf[Int, Unit]
g(1) //works
上面的代码有多脆弱?它目前有效,但当然它不是语言规范的一部分。出于这个原因,当然有可能有一天它不会;我的问题是它的可能性有多大,从编译器的明智选择的意义上理解。我不太了解字节码,但我可以通过尝试将 Int
放入 Unit
寄存器来轻松想象它的中断。另一方面,这种工作实际上可能是文档规范的未记录结果
Scala 编译器和 Java 字节码本身。
我的动力主要在于 @specialized
代码。例如,Iterable
包含以下方法:
trait Iterable[E] /* ... */
def foreach[T](f :E => T) :Unit
可能选择此签名是为了允许传递 E
的任何函数作为参数。
但实际上,它几乎总是 lambda/method 值。上述方法的问题
是它需要专门研究 E
和 T
,导致方法变体的平方数,应该避免。不专注于 T
是没有意义的,因为 scalac 不会为类型参数的子集发出 @specialized
子类:如果任何 @specialized
参数接收到引用(或非专业)类型作为参数,整个对象将是已擦除超类的一个实例。同样,没有apply(Int):Object
方法——如果擦除return类型,那么会调用擦除的apply(Object):Object
,导致装箱。因此,在这种情况下更可取
def foreach(f :E => Unit) :Unit
引入这样的方法(使用不同的名称)并将其用作常规 foreach
的委托目标是可能的,并且不会导致任何装箱,至少只要创建的 lambda 不会没有引用类型作为其 return 类型。
视情况而定。
大多数情况下,Scala 会编译代码,以便返回 Unit
的函数将被调用为 JVM 的 void
(没有返回结果),或者它将其视为返回 scala.Unit
' s 唯一可以丢弃的值。只要 JVM 不会尝试对该返回值调用任何东西,它只会假设它是一些 java.lang.Object
如果用于任何事情,应该检查 scala.Unit
,这永远不会发生。
但这并不意味着不可能。
val f: Int => Int = _ * 2
val g: Unit => Int = _ => 10
(f.asInstanceOf[Int => Unit] andThen g)(10)
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class scala.runtime.BoxedUnit (java.lang.Integer is in module java.base of loader 'bootstrap'; scala.runtime.BoxedUnit is in unnamed module of loader 'app')
scala.Function1.$anonfun$andThen(Function1.scala:85)
scala.Function1.apply$mcII$sp(Function1.scala:69)
ammonite.$sess.cmd27$.<clinit>(cmd27.sc:1)
只要您在自己的代码中调用它,您拥有完全的控制权,始终了解上下文、测试等就应该没问题。
恕我直言,对于世界上基本上每个图书馆或任何将维护一周以上的代码库来说,这是一个错误的假设。
f.andThen(_ => ())
总是更安全,我必须有一些非常好的理由来使用像 .asInstanceOf
这样的优化,并在未来冒一些有趣的错误。
asInstanceOf
是一个本质上不安全的通用逃生舱口。当你使用它时,你实际上是在告诉类型检查器盲目地信任你。
一般来说,当您编写 x.asInstanceOf[Y]
时,您是在向编译器承诺值 x
确实属于 Y
.
只有在 x
确实是 Y
类型的情况下才能保证有效。否则,如果 x
静态已知为基本类型或 null
,编译器将进行一些特殊处理。然后会发生以下两种情况之一:
- Runtime 会识破你的谎言并抛出
ClassCastException
. - 你撒谎就逃脱了,可能还有其他事情打破了界限。
在你的例子中,f 的类型是 Int => Int
,这显然不是 Int => Unit
的子类型。换句话说:你在欺骗编译器,所有保证都是无效的。
这个故事的寓意是:你永远不应该依赖实现细节的正确性,你应该始终将 asInstanceOf
视为不安全的。
正如 Mateusz 所写,f.andThen(_ => ())
或等效的 { x => {f(x); ()}}
是将某些 A => B
转换为 A => Unit
的唯一正确方法。
您还应注意,虽然可以生成将专用函数作为参数的专用方法,但 (x => x).asInstanceOf[Int => Unit]
将始终 return 非专用 Function1[Int,Unit]
,而不是专用类型,所以这对你的性能完全没有帮助。