在Scala 3中,为case构造函数定义抽象方法的推荐方法是什么?

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

Scala 2 有几个从未记录或解释过的“后门规则”,但它们在许多开源项目中使用,甚至成为它们自己的模式,其中一个规则是案例类的伴生对象具有相同的类型一元或空函数的签名,可用于覆盖其外部类型的超类型的属性:

  {
    //  unary function (without eta-expansion) overriden by unary case class constructor

    trait A {
      type CC
      def CC: Int => CC // only works in Scala 2
    }

    object AA extends A {

      case class CC(v: Int) {}
    }
  }

  {
    //  nullary function (without eta-expansion) overriden by nullary case class constructor

    trait A {
      type CC
      def CC: () => CC // only works in Scala 2
    }

    object AA extends A {

      case class CC() {}
    }
  }

在 Scala 3 中,这条规则被撤销,并且没有提出替代方案(每晚使用 Scala 3.3 进行测试):

[Error] /home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/CaseClassOverridingRule.scala:54:18: error overriding method CC in trait A of type => Int => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC;
  object CC has incompatible type

Explanation
===========
I tried to show that
  object com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC
conforms to
  => Int => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC
but none of the attempts shown below succeeded:

  ==> object com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC  <:  => Int => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC class dotty.tools.dotc.core.Types$CachedTypeRef class dotty.tools.dotc.core.Types$CachedExprType  = false

The tests were made under the empty constraint

[Error] /home/peng/git/dottyspike/src/main/scala/com/tribbloids/spike/dotty/CaseClassOverridingRule.scala:69:18: error overriding method CC in trait A of type => () => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC;
  object CC has incompatible type

Explanation
===========
I tried to show that
  object com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC
conforms to
  => () => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC
but none of the attempts shown below succeeded:

  ==> object com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC  <:  => () => com.tribbloids.spike.dotty.CaseClassOverridingRule.AA.CC class dotty.tools.dotc.core.Types$CachedTypeRef class dotty.tools.dotc.core.Types$CachedExprType  = false

大多数 Scala 3 迁移工具不会重写它。结果,我必须手动修改上面的示例,这是唯一的工作案例:

  {
    //  unary function (without eta-expansion) overriden by unary case class constructor

    trait A {
      type CC
      def CC: { def apply(v: Int): CC }
    }

    object AA extends A {

      case class CC(v: Int) {}
    }
  }

  {
    //  nullary function (without eta-expansion) overriden by nullary case class constructor

    trait A {
      type CC
      def CC: { def apply(): CC }
    }

    object AA extends A {

      case class CC() {}
    }
  }

这不是一个理想的替代方案,在结构精化类型中定义CC会导致JVM在运行时以缓慢的反射对其进行验证,并且由于类型擦除而导致验证不完整。此外,DOT 演算中并未完全理解精炼类型,Scala 编译器可能会在后续版本中改变其行为。

如果我需要迁移使用上述规则的库,我应该使用什么类型的签名?

scala case-class scala-3 subtyping
1个回答
0
投票

您会接受基于类型类的解决方案吗?

我们可以定义您想要的特征,不是作为传统的 Java 风格的接口,而是作为带有类型参数

T
并需要
CC
类型和构造函数的类型类。

trait A[T] {
  type CC

  extension(self: T) def CC(n: Int): CC
}

现在我们只编写我们的单例

AA
,没有继承。

object AA {
  case class CC(v: Int) {}
}

此时我们可以手动实现类型类。

given A[AA.type] with
  type CC = AA.CC
  extension(self: AA.type) def CC(n: Int) = AA.CC(n)

但这听起来像是样板文件,如果说 Scala 开发者讨厌一件事,那就是样板文件。所以,让我们试试这个。

import scala.deriving.Mirror

given [T, Aux](using
  sub: T <:< { type CC = Aux },
  mirror: Mirror.ProductOf[Aux],
  paramsSub: mirror.MirroredElemTypes <:< Tuple1[Int],
): A[T] with
  type CC = Aux
  extension(self: T) def CC(n: Int): CC =
    mirror.fromProduct(Tuple(n))

那是一口。让我们来分解一下。我们正在为

any
A[T] 编写
T
的通用实现,以满足一些基本约束。这些约束对应于我们的上下文参数,它们是:

  • sub
    T
    必须是
    { type CC }
    的子类型,我们将内部类型
    Aux
    命名为
    CC
    ,以便我们可以在这里引用它。也就是说,
    T
    必须定义一个名为
    CC
    的类型。请注意,“仅”用于类型级解析的结构类型(例如此类型)没有任何开销,与您建议的结构类型不同,正如您所指出的,这会导致运行时反射惩罚。 mirror:内部类型
  • Aux
  • (或最初称为
    CC
    )必须是产品类型。据我所知,这要么是元组,要么是案例类。
    paramsSub
    :该产品类型内部的成分必须是单个整数。也就是说,
  • Aux
  • 必须恰好包含一个整数。
    
    
    一旦有了这些限制,我们就实施
    A[T]
  • 。我们将
type CC

定义为

Aux
,我们的辅助类型。然后我们编写构造函数。我一分钟前调用的
mirror
对象使我们能够从产品构造我们的实例,因此如果我们有一个正确类型的元组,我们就可以创建我们的实例。
使用示例:
def makeZero[T](t: T)(using a: A[T]): a.CC =
  t.CC(0)

@main def main() = {
  val x: AA.CC = makeZero(AA)
  println(x) // Prints CC(0)
}

Scala 游乐场链接

    

© www.soinside.com 2019 - 2024. All rights reserved.