0

According to the debugger, in the following snippet scala is boxing and unboxing the Char argument and later boxing and unboxing the Boolean result. Why? How to avoid that?

object Test{
    def user(predicate: Char => Boolean): Boolean = {
        predicate('3')
    }

    def main(args: Array[String]): Unit = {

        var b0 = false;
        for( i <- 1 to 100 ) { // warm up
             b0 ||= user(Character.isDefined)
        }
        println(b0)

        // start debugging here
        val b1 = user(Character.isDigit);
        println(b1)
        val b2 = user(c => c == 'a');
        println(b2)
    }
}

As far as I know, the boxing/unboxing is expected when the involved parameters or return type is parameterized. But this is not the case. <= Wrong

Scala version 2.13.3

Readren
  • 994
  • 1
  • 10
  • 18
  • 1
    The **Function** class is parameterized. How to avoid that? Not using **Scala**. Seriously this kind of boxing shouldn't cause you any performance problems and if they do, then you should consider if the JVM is even the appropriate platform. – Luis Miguel Mejía Suárez Nov 22 '20 at 00:50
  • You are right. I am so stupid that I didn't notice that Function1 was used there. – Readren Nov 22 '20 at 00:55
  • Anyway, in my opinion, the compiler should be smart enough to automatically optimize that. Does it? My test was with `specializations` enabled. – Readren Nov 22 '20 at 01:46
  • 1
    Why? Avoid boxing would imply generating extra classes for many of us that would be worse than the boxing. Also, in most cases, it would be irrelevant for example `list.map(f)` what would be the point of optimizing `f` if values in **List** are already boxed. – Luis Miguel Mejía Suárez Nov 22 '20 at 01:49
  • 1
    `Function1` is specialized for return type `Boolean` though. – Jasper-M Nov 22 '20 at 12:43

1 Answers1

4

user takes an argument of type Char => Boolean, which is syntactic sugar for Function1[Char, Boolean]. Function1 is a generic trait from the Scala standard library, and because generics are implemented with erasure on the JVM, the type Char is erased to java.lang.Object at runtime. Thus, the Char needs to be boxed when passing it to the function.

To avoid this, you can define your own trait:

trait CharPredicate {
  def apply(c: Char): Boolean
}

Because this trait isn't generic, no type erasure and hence no boxing is going to take place. You will still be able to use function literal syntax, because Scala allows that for all traits with a single abstract method.

Matthias Berndt
  • 4,387
  • 1
  • 11
  • 25
  • Why would delegating to a function that isn't generic cause boxing? – Matthias Berndt Nov 22 '20 at 01:16
  • I deleted my previous comment to avoid confusion because it was wrong. The boxing that I still see when using the `CharPredicate` is to Integer. No more `Char` nor `Boolean` boxing. So, it is not related to the question. The culprit of the Integer boxing is `MethodHandleNative.linkCallSite(..)` method which has nothing to do with the boxing/unboxing of the parameters and result. – Readren Nov 22 '20 at 01:42
  • 3
    @LuisMiguelMejíaSuárez https://www.diffchecker.com/UNYo9CcU This is bytecode of `Test` with `val b2 = user(c => c == 'a')` corresponding to `def user(predicate: Char => Boolean): Boolean` (on the left) vs. `def user(predicate: CharPredicate): Boolean` (on the right). Look at lines 37-39 (on the left) vs. line 37 (on the right). You can see `BoxesRunTime.boxToCharacter; Function1.apply; BoxesRunTime.unboxToBoolean` vs. `CharPredicate.apply`. Scala 2.13.3. – Dmytro Mitin Nov 22 '20 at 02:43
  • @DmytroMitin oh so it seems the **SAM** actually implemented the method body with the _"function"_ body instead of delegating, I wonder how that could be done if it wasn't a literal nad I just give it a function and then I noticed it didn't work! it has to be literal precisely for that. Today I learned something cool about the SAM thanks Dmytro and thanks Matthias. _(I deleted my two old comments since they are wrong)_ – Luis Miguel Mejía Suárez Nov 22 '20 at 14:41