In Scala2, how to implement match type using implicits?

142 views Asked by At

I'm trying to implement match type using implicit, here is a working example:

  {
    trait FnUnpack[F <: Function1[?, ?]] {
      type II
      type OO
    }

    object FnUnpack {

      implicit def only[I, O]: FnUnpack[I => O] { type II = I; type OO = O } =
        null
    }

    val u = summon[FnUnpack[Int => String]]
    summon[u.II =:= Int]
  }

but with a little twist to make it more accepting to subclasses of Function1, I could easily break it:

  {
    trait FnUnpack[-F <: Function1[?, ?]] {
      type II
      type OO
    }

    object FnUnpack {

      implicit def only[I, O]: FnUnpack[I => O] { type II = I; type OO = O } =
        null
    }

    val u = summon[FnUnpack[Int => String]]
    summon[u.II =:= Int]
//[Error] XXX.scala:36:11: Cannot prove that u.II =:= Int.

  }

if splain plugin is enabled, it is revealed that the compiler automatically assumes that u.II = Nothing

Nothing (reduced from) { u.II } =:= Int
  Cannot prove that u.II =:= Int.

It appears that a contravariance mark complete break the implicit search algorithm! How could this happen and what needs to be done to fix it?

1

There are 1 answers

4
Vrashabh Irde On

To make FnUnpack more accepting to subclasses of Function1 without breaking the implicit resolution mechanism, instead of applying variance to the trait itself, you can use a type bound within the implicit definition? This way, the implicit instance can be more flexible in what it accepts without affecting the type members' inference.

trait FnUnpack[F] {
  type II
  type OO
}

object FnUnpack {
  // Use an implicit def with a type bound
  implicit def only[F <: Function1[I, O], I, O]: FnUnpack[F] { type II = I; type OO = O } = null
}

// Test with a function type
val u = summon[FnUnpack[Int => String]]
summon[u.II =:= Int] // Should compile without issues

Update: For Scala 2 the type inference is not as powerful as in Scala 3, we need to provide more guidance to the compiler. The challenge here is that Scala 2's type inference struggles with inferring type members in this specific context.

One workaround is to use a type class with type parameters rather than type members.

trait FnUnpack[F, II, OO]

object FnUnpack {
  // Define an implicit def for Function1 types
  implicit def only[I, O]: FnUnpack[I => O, I, O] = new FnUnpack[I => O, I, O] {}

  // Helper method to summon FnUnpack instances
  def apply[F, II, OO](implicit ev: FnUnpack[F, II, OO]): FnUnpack[F, II, OO] = ev
}

// Now you can summon the instance for Int => String and check the types
val u = FnUnpack[Int => String, Int, String]