只要忽略返回值,将函数 [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 值。上述方法的问题 是它需要专门研究 ET,导致方法变体的平方数,应该避免。不专注于 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],而不是专用类型,所以这对你的性能完全没有帮助。