1

I want to understand why inlining function fixes runtime error.

The following code results in a runtime error (see bellow)

[error] java.lang.ClassCastException: class Herbivore cannot be cast to class Food (Herbivore and Food are in unnamed module of loader sbt.internal.LayeredClassLoader @4837dd91)
trait GameObject
case class Food(x: Int) extends GameObject
case class Herbivore() extends GameObject


@main 
def main: Unit = 
    val xs = Vector(Food(1), Herbivore(), Food(2))
    
    def minBy[T <: GameObject](f: T => Double): Option[T] =
        xs.collect{case v: T => v}.minByOption(f)
    val done = minBy[Food](food => food.x)
    println(done)

until i change it so minBy is inlined and works as intended(outputs: Some(Food(1))).

inline def minBy[T <: GameObject](f: T => Double): Option[T] = //-snip-

Is this intended behaviour?

JoPoLu
  • 21
  • 2
  • 1
    In Scala 2, you would get a compilation warning `abstract type pattern T is unchecked since it is eliminated by erasure` which can be made a compilation error with appropriate flags (that I strongly recommend). Not sure the same exists in Scala 3 yet. – Gaël J Dec 21 '22 at 19:59

1 Answers1

4

Type erasure.

When JVM sees this code it can see only Vector[GameObject]. Type test on generic parameter, well, it cannot be done because this parameter was not passed into method in runtime, so this check always succeeds.

def minBy(f: GameObject => Double): Option[GameObject] =
  xs.collect{case v => v} // collects everything
    .minByOption(f) // will run f against all values of xs

Since all values of xs will be passed into f in minByOption and you have there not only Food, it will meed unexpected type.

In Scala 2 you can add runtime test with ClassTag but I see that you are using Scala 3 which has TypeTest which in your case should work with its Typeable alias.

import scala.reflect.Typeable
def minBy[T <: GameObject: Typeable](f: T => Double): Option[T] =
  xs.collect{case v: T => v}.minByOption(f)

It should behave as if you written manually:

def minBy[T <: GameObject](f: T => Double)(using tt: Typeable[T]): Option[T] =
   xs.collect{ case tt(v) => v } // tt is providing unapply (extractor)
     .minByOption(f)
Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64