Search code examples
scalajvm-bytecode

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

How brittle is the above code? It works currently, but of course it is not a part of language specification. For this reason, it is of course possible, that one day it will not; my question is how likely it would be, understood in the sense of sensible alternatives for the compiler. I do not know bytecode well, but I could easily imaging it breaking by trying to put an Int into a Unit register. On the other hand, this working can actually be an undocumented consequence of documented specification of the Scala compiler and Java bytecode itself.

My motivation lies primarly in @specialized code. For example, Iterable contains the following method:

trait Iterable[E] /* ... */
    def foreach[T](f :E => T) :Unit

This signature was probably chosen to allow passing any function of E one might have as an argument. In practice though, it will almost always be a lambda/method value. The problem with the above method is that it would require specializing on both E and T, leading to a square number of method variants, which should be avoided. Not specializing on T is pointless, because scalac doesn't emit @specialized subclasses for subsets of type parameters: if any of the @specialized parameters receive a reference (or a non-specialized) type as the argument, the whole object will be an instance of the erased superclass. Similarly, there is no apply(Int):Object method - if the return type is erased, then the erased apply(Object):Object will be called, resulting in boxing. So, it would be much more desirable in this case to have

def foreach(f :E => Unit) :Unit

Introducing such a method (with a different name) and using it as the delegate target of the regular foreach is possible and doesn't result in any boxing, at least as long as the created lambda doesn't have a reference type as its return type.


Solution

  • Depends on the context.

    Most of the time Scala will compile code so that function returning Unit will be called as JVM's void (no returned result) or it will treat it as returning scala.Unit's only value which can be discarded. As long as JVM won't try to call anything on this returned value it will only assume that it is some java.lang.Object that should be checked for scala.Unit if used for anything, which could never happen.

    But it doesn't mean it's impossible.

    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)
    

    As long as you call it in your own code, that you have full control, always know the context, test, etc it should be fine.

    Which IMHO is a bad assumption for basically every library in the world or any codebase that would be maintained more than one week.

    f.andThen(_ => ())
    

    would always be safer and I would have to have some really good reasons to use optimization like .asInstanceOf and risk some funny errors in the future.