如何解决只要我忽略返回值,将函数 [X, Y] X => Y 转换为 X => () 是否安全?
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/方法值。上述方法的问题
是它需要对 E
和 T
进行专门化,从而导致方法变体的平方数,应该避免这种情况。不专门研究 T
是没有意义的,因为 scalac 不会为类型参数的子集发出 @specialized
子类:如果任何 @specialized
参数接收引用(或非专门化)类型作为参数,整个对象将是被擦除超类的一个实例。同样,没有 apply(Int):Object
方法——如果返回类型被擦除,那么被擦除的 apply(Object):Object
将被调用,导致装箱。因此,在这种情况下更可取的是
def foreach(f :E => Unit) :Unit
引入这样的方法(具有不同的名称)并将其用作常规 foreach
的委托目标是可能的,并且不会导致任何装箱,至少只要创建的 lambda 没有有一个引用类型作为它的返回类型。
解决方法
取决于上下文。
大多数时候 Scala 会编译代码,以便返回 Unit
的函数将被称为 JVM 的 void
(不返回结果),或者将其视为仅返回 scala.Unit
可以丢弃的值。只要 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$1(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]
将始终返回未专用的 Function1[Int,Unit]
,而不是专用类型,因此这无济于事你完全有表现。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。