我有以下简单的程序,分别为类型参数和抽象类型别名定义2个相同的上限:
package scala.spike.typeBoundInference
object Example1 {
trait Domain {
}
trait Impl {
type DD <: Domain
type GG <: StaticGraph[DD]
type A1
type A2
type A3
// ... this type list can go very long
// so inlining them as generic type parameters is impossible
final type Builder = StaticGraph.Builder[DD, GG]
}
trait DSL[I <: Impl] {
val impl: StaticGraph.Builder[I#DD, I#GG]
}
trait StaticGraph[T <: Domain] {}
object StaticGraph {
trait Builder[D <: Domain, G <: StaticGraph[D]] {}
}
}
但是,scala拒绝编译它:
错误:(16,27)类型参数[I#DD,I#GG]不符合trait Builder的类型参数bounds [D <:scala.spike.typeBoundInference.Example1.Domain,G <:scala.spike.typeBoundInference。 Example1.StaticGraph [D]] val impl:StaticGraph.Builder [I#DD,I#GG]
这可能会出错?
斯卡拉没有理由认为它不安全。
在此期间,我发现如果将类StaticGraph [T]声明为协变scala编译器将成功运行。这更糟糕(由于某种原因,StaticGraph [T]必须是不变的),因为类型绑定GG <:StaticGraph [DD]意味着如果确定了类型DD,那么GG是StaticGraph [DD]的子类,但不是必需的StaticGraph [Domain]的子类,这正是我想要的。
更新1:我已经阅读了所有的答案和评论,并以某种方式得到的印象是,核心原因是无法保证i
的任何实例Impl
,类型绑定只保证类型
i.DD <:< Impl#DD
和Imp#GG <:< StaticGraph[Impl#DD]
但不是StaticGraph[i.DD] <:< StaticGraph[Impl#GG]
因此i.GG <:< StaticGraph[i.DD]
也不能保证。
但是,我已经做了一个快速的实验来验证这个想法,结果证明这不是真的:
object Example1 {
trait Domain {}
class D1 extends Domain {}
trait Impl {
type DD <: Domain
type GG <: StaticGraph[DD]
}
class StaticGraph[T <: Domain] {}
object Impl1 extends Impl {
type DD = D1
type GG = StaticGraph[Domain]
}
//or this:
val impl = new Impl {
type DD = D1
type GG = StaticGraph[Domain]
}
}
在这种情况下编译器抛出一个错误:
错误:(19,10)在特征Impl中覆盖类型GG,边界为<:scala.spike.TypeBoundInference.Example1.StaticGraph [scala.spike.TypeBoundInference.Example1.Impl1.DD];类型GG具有不兼容的类型类型GG = StaticGraph [Domain]
如果您认为类型约束不适用于某些情况,您能举例说明吗?
更新2:事实证明,根据答案,这是真的:
i.GG <:< StaticGraph[i.DD]
但这可能是假的:
Impl#GG <:< StaticGraph[Impl#GG]
。
所以在DSL的背景下,这也可能是错误的:
I#GG <:< StaticGraph[I#GG]
(3)
但这只是谜题的一部分,为了证明它是不安全的类型,我们必须构建一个DSL [I]的反例,它使条件(3)无效。所以旧的问题仍然存在:是否有可能构建一个反例?
这可能会出错?
GG <:StaticGraph [DD]检查
通过声明type GG <: StaticGraph[DD]
,您可以在成员类型之间建立关系(它与<: StaticGraph[this.DD]
相同)。这意味着您需要考虑Impl
的实例。
对于任何val i: Impl
,你有i.DD <: Domain
和i.GG <: StaticGraph[i.DD]
。你也有i.DD <: I#DD
。但你没有i.DD =:= I#DD
!所以StaticGraph[i.DD]
和StaticGraph[I#DD]
没有关系(对于不变的StaticGraph
)。所以i.GG
(或I#GG
)和StaticGraph[I#DD]
都不是。
要使其编译,您需要要求所有i.DD
都相同(这也保证i.DD =:= I#DD
)。有一种方法可以做到这一点:
trait DSL[T <: Domain, I <: Impl { type DD = T } ]
将使代码编译(没有任何其他更改)。
如果StaticGraph
是协变的,那么关系就会成功:
I#GG =:= (kind of)
i.GG forSome { val i: I } <:
StaticGraph[i.DD] forSome { val i: I } <:
StaticGraph[I#DD] forSome { val i: I } =:=
StaticGraph[I#DD]
好问题解决了:
import scala.language.higherKinds
object Example5 {
trait Domain {}
trait D1 extends Domain
trait Impl {
type DD <: Domain
type GG[T <: Domain] <: StaticGraph[T]
}
trait DSL[I <: Impl] {
val impl: Builder[I#DD, I#GG]
}
trait StaticGraph[T <: Domain] {}
trait Builder[D <: Domain, G[T <: Domain] <: StaticGraph[T]] {}
}
我无法相信我必须使用更高级别的这种平庸的事情: - <
为什么编译?它解耦类型约束并将其延迟到必要时。 (这是我能想到的唯一解释)