Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible in Scala 3 to do pattern matching by generic type?

I'm trying to migrate my mind from Java background (and a bit of Scala 2) to Scala 3 and to implement a kind of version of PartialFunction.orElse but with union type as an input parameter instead of intersection type:

trait F[A, B] extends (A => B) {
  
  def orElse[A1, B1](other: F[A1, B1]): F[A | A1, B | B1] = {
    val self = this
    
    new F[A | A1, B | B1] {
      override def apply(v: A | A1): B | B1 = {
        helper(v)
      }
    
      transparent inline def helper(v: A | A1): Any = {
        inline v match {
          case _: A => self.apply(v.asInstanceOf[A])
          case _: A1 => other.apply(v.asInstanceOf[A1])
        }
      }
    }
  }
}

But I ended up with a fact that both calls go to the first branch of "match":

 val f1 = new F[Int, String] {
    override def apply(v: Int): String = s"Int => String : $v"
  }

  val f2 = new F[String, Int] {
    override def apply(v: String): Int = v.length
  }

  val f = f1.orElse(f2)

println(f(42)) // prints "Int => String : 42"
println(f("hello")) // fails in runtime because of trying casting String to Int

So, my question is, is it even possible? If yes, what am I missing?

like image 719
wlad031 Avatar asked Aug 24 '21 20:08

wlad031


1 Answers

The helper may be inlined, but the entire trait isn't, and neither is orElse, so A and A1 are still erased. You should have gotten a warning from the compiler saying that A and A1 cannot be checked at runtime. To do that, you need to have a Typeable[A] and a Typeable[A1] in scope (TypeTest[A | A1, A] and a TypeTest[A | A1, A1] should've worked too, since they're more specific, but for whatever reason, the compiler doesn't use them).

I decided to go with extension methods instead of a trait because it's just simpler that way. This way, you can use it for normal functions too, as long as they have a Typeable instance in scope (I believe instances for Int, String, and other primitives/classes without type parameters or members are synthesized automatically). (Scastie)

import scala.reflect.Typeable

extension [A1: Typeable, B1](f1: A1 => B1)
  def orElse[A2: Typeable, B2](f2: A2 => B2): (A1 | A2) => (B1 | B2) =
    case v1: A1 => f1(v1)
    case v2: A2 => f2(v2)

val f1 = (v: Int) => s"Int => String : $v"
val f2 = (v: String) => v.length
val f = f1.orElse(f2)

However, explicitly checking types feels like an antipattern to me, so it would help if you could give more details about your bigger problem.

like image 68
user Avatar answered Nov 15 '22 05:11

user