TypeTest 克服 scala3 中泛型类型的擦除问题

win*_*son 3 reflection scala scala-3

TypeTest我在理解scala3 中的 s 如何替代scala 2 中的 s 时遇到问题。TypeTag用例能够匹配像 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] => ???
Run Code Online (Sandbox Code Playgroud)

编译器警告中的编译结果(如预期)

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] => ???
Run Code Online (Sandbox Code Playgroud)

以及一个简单的 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?
Run Code Online (Sandbox Code Playgroud)

我尝试在网上搜索答案,但由于这是相当新的,所以我并不幸运。有什么提示吗?

use*_*ser 5

这里的问题是 aTypeTest[Any, Mod[T]]将能够检查 an 是否Any是 a Mod[T],而不是检查 a 是否Mod[T]是 aMod[Foo.Bar]或 a 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")
Run Code Online (Sandbox Code Playgroud)

我不知道为什么 simplebarMod: Mod[Foo.Bar]不能与TypeTest[Any, Mod[Foo.Bar]]in 范围一起使用,我稍后会讨论这个问题。

但是,您现在必须TypeTest自己实际实现这些。由于 JVM 没有具体化,因此您必须TMod类中存储相关信息。如果你真的想保留Mod一个案例类,你可以这样做:

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])
Run Code Online (Sandbox Code Playgroud)

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]])
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

为了方便起见,apply可以创建一个内联方法:

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

你可以像这样使用它(Scastie):

modifyBarsOrBaz(Mod[Foo.Bar](bar => bar))  //barMod
modifyBarsOrBaz(Mod[Foo.Baz](baz => baz))  //bazMod
Run Code Online (Sandbox Code Playgroud)

但实际上,对这样的类型测试的需求对我来说就像是一种代码味道。我建议重新考虑你的设计来解决这个问题,而不是使用这样的标签。