Scala 2と3でcase classのコンパニオンのunapply(Tupleへの変換)が互換がないことの解決案

まずは以下をご覧ください

github.com

以下のようなcase classがあったときにAのコンパニオンのunapplyは、それぞれ存在するが、戻り値型が異なっており

case class A(x: Int, y: String)
  • Scala 2: Option[(Int, String)] がかえる
  • Scala 3: A がそのままかえる

という違いがあります。

この違いは、case classのapplyやunapplyを(明示的に)渡して型クラスのインスタンスを生成するようなライブラリその他(例えば各種jsonのライブラリ、slickの <> でのマッピングなど)で、面倒なことになります。

  • ライブラリ側が対応してくれればいいけれど、それもどうなるのかわからない
  • Scala 3では Tuple.fromProductTyped でTupleには変換できるが、それの存在だけではだめで、もう少し工夫しないと、Scala 2とScala 3で完全なcross buildができない
  • 若干話が逸れるけれど、case classのコンパニオンに共通の親を継承させる(親だけ2と3で別にする) 作戦をやろうとしたら java.lang.VerifyError: Bad type on operand stack になってつらい https://github.com/lampepfl/dotty/issues/12557

というわけで もう少し工夫しないと、Scala 2とScala 3で完全なcross buildができない を、Scala 2の場合のみshapelessに依存させて解決する案がこちらです。

github.com

github.com

Scala 2

import shapeless.ops.product.ToTuple

object AsTuple {
  implicit class AsTupleOps[A](private val value: A) extends AnyVal {
    def asTuple[B](implicit toTuple: ToTuple.Aux[A, B]): B =
      toTuple.apply(value)
  }
}

Scala 3

import scala.deriving.Mirror.ProductOf

object AsTuple {
  extension [A <: Product](value: A) {
    def asTuple(using mirror: ProductOf[A]): mirror.MirroredElemTypes =
      Tuple.fromProductTyped(value)
  }
}

本来依存ゼロにするにはshapeless使わないほうが良いと思いますが、誰か・・・。

あるいは、もっとすごく簡単というかシンプルな方法が発見されたら教えて下さい。 (もしくはScala 3かScala 2の本体で機能追加されたら、追記するかもしれませんが・・・)