TypeTest для преодоления стирания с помощью универсальных типов в scala3

Я не понимаю, как TypeTests в scala3 может заменить использование TypeTag в scala 2. Вариант использования, позволяющий сопоставить общий параметр, например x: List[Int].

Конкретный пример, который я пытаюсь решить:

      enum Foo :
  case Bar()
  case Baz()

case class Mod[T <: Foo](modFn: T => T)

def modifyBarsOrBaz(mod: Mod[_]) = mod match
  case barMod: Mod[Foo.Bar] => ???
  case bazMod: Mod[Foo.Baz] => ???

результаты компиляции (как и ожидалось) в предупреждениях компилятора

the type test for Mod[Foo.Bar] cannot be checked at runtime и недостижимый случай.

Теперь у меня вопрос: возможно ли это вообще в scala3?

У меня создалось впечатление, что мне нужно каким-то образом TypeTest[Any, Mod[Foo.X]] для всех X, которые являются подклассами Foo перечисление.

Но я изо всех сил пытаюсь реализовать эти тесты, а также понимаю, что using параметр modifyBarsOrBaz требуется, чтобы это работало.

Таким образом, я придумал следующее (не работающее) решение:

      def modifyBarsOrBaz[T <: Foo](mod: Mod[T])(using TypeTest[Any, Mod[T]]) = mod match
  case barMod: Mod[Foo.Bar] => ???
  case bazMod: Mod[Foo.Baz] => ???

и наивная реализация tt как таковая

      val tt: TypeTest[Any, Mod[Foo.Bar]] =
  new TypeTest[Any, Mod[Foo.Bar]] :
    def unapply(x: Any): Option[x.type & Mod[Foo.Bar]] = x match
      case m: Mod[_] => ??? // what to do here? use a classtag on Mod?

Я попытался поискать ответы в Интернете, но так как это довольно ново, мне не повезло. Какие-нибудь намеки?

1 ответ

Проблема здесь в том, что TypeTest[Any, Mod[T]] сможет проверить, есть ли Any не проверять, действительно ли Mod[T] это Mod[Foo.Bar] или Mod[Foo.Baz]. Вам понадобится TypeTest[Any, Mod[Foo.Bar] а также TypeTest[Any, Mod[Foo.Baz]:

      def modifyBarsOrBaz(mod: Mod[?])(using asBar: TypeTest[Any, Mod[Foo.Bar]], asBaz: TypeTest[Any, Mod[Foo.Baz]]) =
  mod match
    case asBar(barMod) => println("barMod")
    case asBaz(bazMod) => println("bazMod")

Я не знаю почему простой barMod: Mod[Foo.Bar] не работал с TypeTest[Any, Mod[Foo.Bar]] по объему, я вернусь к этому позже.

Однако теперь вам нужно реализовать их самостоятельно. Поскольку у JVM нет реификации, вам придется хранить информацию о Tв классе. Если ты действительно хочешь сохранить Mod один класс case, вы можете сделать это:

      type BarOrBaz[T <: Foo] <: String = T match {
  case Foo.Bar => "Bar"
  case Foo.Baz => "Baz"
}

case class Mod[T <: Foo](modFn: T => T, tag: BarOrBaz[T])

TypeTest экземпляры для Bar а также Baz теперь может быть предоставлен встроенный метод:

      import compiletime.constValue

inline given [T <: Foo]: TypeTest[Mod[?], Mod[T]] = new TypeTest:
  def unapply(mod: Mod[?]) = Option.when(mod.tag == constValue[BarOrBaz[T]])(mod.asInstanceOf[mod.type & Mod[T]])

modifyBarsOrBaz было бы

      def modifyBarsOrBaz(mod: Mod[?])(using asBar: TypeTest[Mod[Foo], Mod[Foo.Bar]], asBaz: TypeTest[Mod[Foo], Mod[Foo.Baz]]) = mod match
  case asBar(barMod) => println("barMod")
  case asBaz(bazMod) => println("bazMod")

Для удобства встроенный apply метод может быть сделан:

      object Mod:
  inline def apply[T <: Foo](modFn: T => T) = new Mod(modFn, constValue[BarOrBaz[T]])

И ты мог бы использовать это так (Скэсти ):

      modifyBarsOrBaz(Mod[Foo.Bar](bar => bar))  //barMod
modifyBarsOrBaz(Mod[Foo.Baz](baz => baz))  //bazMod

Но на самом деле необходимость в типовых тестах, подобных этому, кажется мне запахом кода. Я бы посоветовал переосмыслить ваш дизайн, чтобы обойти это, а не использовать подобные теги.

Другие вопросы по тегам