如何在 Scala 中将 F 有界多态与关联类型结合起来?

How to combine F-bounded polymorphism with associated types in Scala?

我有一个叫做 Graphlike 的特性,它可以作为图表使用。值得注意的是,我想要的属性之一是方法 g.subgraph(Set(1, 2, 3)) 会 return 一个相同类型的子图,只有顶点 1、2 和 3。显然,这意味着我想要 F-有界多态性 Graphlike 看起来像这样:

trait Graphlike[A <: Graphlike[A]] {
  type Vertex

  def subgraph(selectedVertices: Set[Vertex]): A
}

我还有一个特征,它代表一个自动机,具有关联的边和顶点类型。我希望它表现得像一个图表。简化后,它看起来像这样:

trait Automaton extends Graphlike[Automaton] {
  type State
  type Vertex = State

  def states: Iterable[State]
  def initialState: State
}

这几乎行得通。但是,当我尝试将两者混合并对结果做一些有用的事情时,Scala 的类型系统变得混乱:


class UsesAutomataAsGraphs(val aut: Automaton with Graphlike[Automaton]) {
  aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}

给出如下错误:

[info] Compiling 1 Scala source to /Users/albin/Downloads/example/target/scala-2.12/classes ...
[error] /Users/albin/Downloads/example/src/main/scala/example/Example.scala:21:56: type mismatch;
[error]  found   : UsesAutomataAsGraphs.this.aut.State
[error]  required: _1.State where val _1: Automaton
[error]   aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))

如何让 Scala 理解这两个关联类型在所有派生对象中都是相同的?

最简单的解决方案似乎是这个。只需确保子图 returns 与 this.type 的类型相同即可。不需要 A - 它只会增加额外的复杂性,因为您试图证明 Athis.

的类型
trait Graphlike {
  type Vertex

  def subgraph(selectedVertices: Set[Vertex]): this.type
}

trait Automaton extends Graphlike {
  type State
  type Vertex = State

  def states: Iterable[State]
  def initialState: State
}

class UsesAutomataAsGraphs(val aut: Automaton) {
  aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}

在斯卡斯蒂:https://scastie.scala-lang.org/zMtde7VISKi18LdPXO6Ytw



State 成为类型参数也对我有用。请注意,在 UsesAutomataAsGraphs 中,如果您使用 A <: Automaton[_](通配符),它不起作用,因为 State 可以是任何东西。编译器希望你保证返回的 Automaton 将具有相同的 State 类型(因为它是无界的,其他 类 扩展 Automaton 可以不同地定义它)。

trait Graphlike[A <: Graphlike[A]] {
  type Vertex

  def subgraph(selectedVertices: Set[Vertex]): A
}

trait Automaton[State] extends Graphlike[Automaton[State]] {
  type Vertex = State

  def states: Iterable[State]
  def initialState: State
}

class UsesAutomataAsGraphs[S](val aut: Automaton[S]) {
  aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}

Link 给斯卡斯蒂:https://scastie.scala-lang.org/RolPc3ggTxeZ2tUqdXKNEQ


如果您这样定义 subgraph,它也有效:

def subgraph(selectedVertices: Set[_ >: Vertex]): this.type

因为它是逆变的,即使 Vertex 在不同的 类 and/or 特征中不同,它也会起作用。

Link 给斯卡斯蒂:https://scastie.scala-lang.org/fz509HEpTBGoJGaJxLziBQ

首先,写 val aut: Automaton with Graphlike[Automaton] 是没有意义的,因为 Automaton extends Graphlike[Automaton](所以 Automaton with Graphlike[Automaton] =:= Automaton)。

其次,我猜你希望 GraphlikeVertex 成为 Automaton 中的 State。所以你应该添加 override type Vertex = StateAutomaton.

第三,Vertex是路径依赖类型。 aut.Vertexa.Vertex(其中 val a: Automaton = aut.subgraph(Set(aut.initialState)))是不同的类型。如果你想让 subgraph 为不同的 x: Automaton 接受 Set[x.Vertex] 那么你应该使用类型 projection

修改它的签名
def subgraph(selectedVertices: Set[A#Vertex]): A

(整个代码:https://scastie.scala-lang.org/pKfCrEjDToOXi0e7fDEt7w

另一种修改subgraph签名的方法是(如@user所建议)

def subgraph(selectedVertices: Set[Vertex]): this.type

因此您应该扩展参数类型(从 Set[Vertex] 又名 Set[this.Vertex]Set[A#Vertex])或缩小 return 类型(从 Athis.type).