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

问题描述 投票:0回答:1

这是一个简单的例子:


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 无法解析为具体类和/或运行时条件的情况。

scala pattern-matching type-erasure scala-3 dotty
1个回答
1
投票

在 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
。要么(方法 1)

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.Typeable
(
scala.reflect.TypeTest
) 代替
ClassTag
。方法1有效

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

我仍然认为

given
using
在所有情况下都可以避免。这两种方法都需要大量样板,特别是当模式匹配包含大量并集或交集类型时

在上一个示例中,使用

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

ClassTag
TypeTag
shapeless.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 Int
T1 {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
© www.soinside.com 2019 - 2024. All rights reserved.