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 编译器可能会在后续版本中改变其行为。
如果我需要迁移使用上述规则的库,我应该使用什么类型的签名?
您会接受基于类型类的解决方案吗?
我们可以定义您想要的特征,不是作为传统的 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 游乐场链接