在 Scala3 中,如果泛型类型参数映射到依赖类型,协变和逆变修饰符是如何映射的?



在本文中,解释了Scala 3代码中的每个泛型类型参数都被视为符合DOT逻辑的依赖类型:

https://dotty.epfl.ch/docs/internals/higher-kinded-v2.html

即:

二元性核心思想:一个参数化的类,比如

class Map[K, V]

被视为等同于具有类型成员的类型:

类映射{类型映射$K;映射$V}

(文章可能过时了,但设计理念应该仍然有效)

因此,我期望任何逆变&类型参数的协变修饰符也被重写,所以我做了一个快速的实验,编译器应该能够转换以下代码:

object AsArg {
trait P[+TT] {
val vv: TT
}
trait P1 extends P[Product] {
val vv: Product
}
trait P2 extends P1 with P[Tuple1[Int]] {
val vv: Tuple1[Int]
}
}

这:

object AsDependentType {
trait P {
type TT
val vv: TT
}
trait P1 extends P {
type TT <: Product
val vv: Product
}
trait P2 extends P1 with P {
type TT <: Tuple1[Int]
val vv: Tuple1[Int]
}
}

具有讽刺意味的是,在转换之后,编译器抛出以下错误:

[Error] ...CovariantDependentType.scala:30:11: error overriding value vv in trait P of type P1.this.TT;
value vv of type Product has incompatible type
[Error] ...CovariantDependentType.scala:36:11: error overriding value vv in trait P of type P2.this.TT;
value vv of type Tuple1[Int] has incompatible type
two errors found

转换后正确的等效代码是什么?

In/co/contro -variance是类型构造函数F[T]的一个属性。

F是协变的,如果对所有A <: B,F[A] <: F[B]。对于所有A <: B,F[B] <: F[A],F是逆变的。如果A <: BF[A]F[B]<:不相关,则F不变。

在Scala 2和Scala 3中,类型参数和类型成员是不同的。

对于类型参数,可以在声明点设置方差

trait F[+T] // co-variance
trait F[-T] // contra-variance
trait F[T] // invariance

或在呼叫现场

trait F[T]
type G[+T] = F[_ <: T] // co-variance
type G[-T] = F[_ >: T] // contra-variance

在Java中没有+/-,所以类型参数的方差必须总是在调用时设置为? extends ...,? super ...

对于Scala中的类型成员,也没有+/-,因此必须在调用站点

设置方差
trait F { type T }
type G[+U] = F { type T <: U } // co-variance 
type G[-U] = F { type T >: U } // contra-variance

类型参数代码
trait P[+TT] {
val vv: TT
}
trait P1 extends P[Product] {
val vv: Product
}
trait P2 extends P1 with P[Tuple1[Int]] {
val vv: Tuple1[Int]
}

可以在Scala 2中转换为类型成员代码

trait P {
type TT
val vv: TT1 forSome {type TT1 >: TT} // just val vv: _ >: TT is illegal here: unbound wildcard type
}
trait P1 extends P {
type TT <: Product
val vv: Product
}
trait P2 extends P1 with P {
type TT <: Tuple1[Int]
val vv: Tuple1[Int]
}

TT1 forSome {type TT1 >: TT} =:= Any起,它与

相同
// (*)
trait P {
type TT
val vv: Any
}
trait P1 extends P {
type TT <: Product
val vv: Product
}
trait P2 extends P1 with P {
type TT <: Tuple1[Int]
val vv: Tuple1[Int]
}

由于存在类型在Scala 3中被推荐翻译为路径依赖类型,因此可以在Scala 3中翻译为(*)或

trait P {
type TT
trait Inner {
type TT1 >: TT
val vv: TT1
}
val i: Inner
}
trait P1 extends P {
type TT <: Product
}
trait P2 extends P1 with P {
type TT <: Tuple1[Int]
}

Prof @DmytroMitin的答案很可能接近原始命题中实际编译的JVM字节码。如果依赖于纯DOT逻辑,其约束可能不像原始版本那样严格。这应该不是问题,因为编译器可以叠加额外的验证步骤(不属于DOT逻辑)以确保相同的安全级别。

但是我会发布我的答案,完全依赖于DOT逻辑,这涉及到动态地为类型参数的每个组合生成一个伴随的泛型对象:


object AsDependentType {
trait Gen {
type Upper
type _CoV = { type T <: Upper }
trait CoV extends P {
type T <: Upper
val vv: Upper
}
}
trait P
object GenY extends Gen {
type Upper = Product
trait P1 extends P with CoV {
val vv: Product
}
}
object GenZ extends Gen {
type Upper = Tuple1[Int]
trait P2 extends GenY.P1 with P with CoV {
val vv: Tuple1[Int]
}
}
implicitly[P with GenZ._CoV <:< P with GenY._CoV]
}

Gen接口可以是具有1个参数的所有高等类型的单个trait。冠状病毒病的临时实施;当。时也会产生矛盾F[A]<:<F[B]需要证明,例如:

P[Tuple1[Int]] <:< P[Product]

现在变成了:

P with GenZ.CoV <:< P with GenY.CoV

除了DOT之外不需要额外的逻辑,此时,编译器错误阻止它成功编译(vv: Upper降级为Product而不是GenZ.Upper),但如果这是固定的,它应该没有问题替换协变类型构造函数

更新1:这可能意味着Scala 3放弃一般类型投影的原因与放弃不可预测性完全相同,因为两者都允许将1类降级为类型/0类,这会导致与逻辑的其他特征(特别是联合类型)的各种矛盾

最新更新