在Scala 3中,使用擦除类型进行模式匹配的规范方法是什么?

cyej8jka  于 2023-02-16  发布在  Scala
关注(0)|答案(1)|浏览(156)

下面是一个简单的例子:

object MatchErasedType {

  trait Supe {
    self: Singleton =>

    type T1
    lazy val default: T1

    def process(v: Any): T1 = {
      v match {
        case vv: T1 => vv
        case _ => default
      }
    }
  }
}

它将抛出编译器警告:

MatchErasedType.scala:13:14: the type test for Supe.this.T1 cannot be checked at runtime

这个例子很有趣,因为Supe的任何示例都可以保证是Singleton,所以process的延迟物化不会有任何擦除,因此,这个问题实际上包含两种不同的情况:

  • 如果Supe的所有示例都是专门化的Singleton,那么应该如何消除它,而不使用任何隐式召唤或转换?
  • 其他情况下如何消除?
    • 更新1**:需要注意的是,v: Any的类型在编译时是未知的,调用点也无法提供这样的信息。因此,当T1不能被解析为一个具体的类和/或运行时条件时,这个问题就不适用了。
4uqofj5v

4uqofj5v1#

在Scala 2中

trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1

  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1

为了克服类型擦除,在Scala 2中我们使用scala.reflect.ClassTag

trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1

  def process(v: Any)(implicit ct: ClassTag[T1]): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

或(方法2)

trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1

  implicit def ctag: ClassTag[T1]

  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
  override implicit def ctag: ClassTag[T1] = {
    val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
    implicitly[ClassTag[T1]]
  }
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
  override implicit def ctag: ClassTag[T1] = {
    val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
    implicitly[ClassTag[T1]]
  }
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

类似地,在Scala 3中

trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1

  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1

在Scala 3中,使用scala.reflect.Typeablescala.reflect.TypeTest)代替ClassTag

trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1

  def process(v: Any)(using Typeable[T1]): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

关于方法2,在Scala 3中没有办法隐藏隐式的名称,所以让我们把它放在一个局部作用域中,这样就不会在对象隐式作用域中找到它

trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1

  def tt: Typeable[T1]

  def process(v: Any): T1 = {
    given Typeable[T1] = tt

    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
  }
}

object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
  override def tt: Typeable[T1] = summon
}

object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
  override def tt: Typeable[T1] = summon
}

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

我仍然认为givenusing在所有情况下都可以避免,这两种方法都需要大量的样板,特别是在模式匹配包含大量联合或交集类型时
在上一个示例中,使用T1 with IntT1 {def v: Int}等类型添加每个新case都需要一个Typeable,这在许多情况下是不可行的。
ClassTag , TypeTagshapeless.Typeable/TypeCase(Scala 2)、TypeTest / Typeable、Shapeless-3 Typeable(Scala 3)是克服类型擦除的标准工具。在运行时匹配类型对于模式匹配来说并不常见。如果您的业务逻辑是基于类型的,那么您可能根本不需要模式匹配,也许类型类会是更好的选择

trait Supe:
  self: Singleton =>

  type T1 <: Singleton
  lazy val default: T1

  trait Process[A]:
    def process(a: A): T1

  trait LowPriorityProcess:
    given low[A]: Process[A] with
      def process(a: A): T1 = { println(2); default }
  object Process extends LowPriorityProcess:
    given [A <: T1]: Process[A] with
      def process(a: A): T1 = { println(1); a }

  def process[A: Process](v: A): T1 =
    summon[Process[A]].process(v)

object Impl1 extends Supe:
  override type T1 = Impl1.type
  override lazy val default: T1 = this

object Impl2 extends Supe:
  override type T1 = Impl2.type
  override lazy val default: T1 = this

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

trait Process[A, T1 <: Singleton]:
  def process(a: A, default: T1): T1

trait LowPriorityProcess:
  given low[A, T1 <: Singleton]: Process[A, T1] with
    def process(a: A, default: T1): T1 =
      {println(2); default}
object Process extends LowPriorityProcess:
  given[A <: T1, T1 <: Singleton]: Process[A, T1] with
    def process(a: A, default: T1): T1 =
      {println(1); a }

trait Supe:
  self: Singleton =>

  type T1 <: Singleton
  lazy val default: T1

  def process[A](v: A)(using p: Process[A, T1]): T1 =
    p.process(v, default)

object Impl1 extends Supe:
  override type T1 = Impl1.type
  override lazy val default: T1 = this

object Impl2 extends Supe:
  override type T1 = Impl2.type
  override lazy val default: T1 = this

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

使用T1 with IntT1 {def v: Int}等类型添加每个新案例都需要使用Typeable,这在许多情况下是不可行的。

trait Supe:
  self: Singleton =>

  type T1 <: Singleton
  lazy val default: T1

  def tt[S <: T1]: Typeable[S]

  def process(v: Any): T1 =
    given Typeable[T1] = tt
    given tt1: Typeable[T1 with SomeTrait] = tt
    given tt2: Typeable[T1 {def v: Int}] = tt
    //...

    v match
      case vv: T1 => {println(1); vv}
      case _      => {println(2); default}

trait SomeTrait

object Impl1 extends Supe:
  override type T1 = Impl1.type with SomeTrait
  override lazy val default: T1 = this.asInstanceOf[T1]
  override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]

object Impl2 extends Supe:
  override type T1 = Impl2.type {def v: Int}
  override lazy val default: T1 = this.asInstanceOf[T1]
  override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]

Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2

相关问题