【问题标题】:Building instances of `this.type` in Scala在 Scala 中构建 `this.type` 的实例
【发布时间】:2020-05-22 12:12:21
【问题描述】:

我正在研究一个名为 Graphlike 的图形特征,我在其中使用依赖/关联类型作为顶点。在solving my polymorphism issues 之后,我的实现如下所示:

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
  def getBuilder: AutomatonBuilder[this.type]


  def subgraph(selectedVertices: Set[Vertex]) = {
    val builder = getBuilder
    // Some logic to actually do something to the builder here
    builder.getAutomaton
  }
}

trait AutomatonBuilder[A <: Automaton] {
  def getAutomaton: A
}

但是,当我尝试实际实现一个具体的自动机时,我遇到了麻烦:

class ConcreteAutomaton extends Automaton {
  type State = Int

  def states = List(1, 2, 3)
  def initialState = 1
  def getBuilder = new ConcreteAutomatonBuilder

}

class ConcreteAutomatonBuilder extends AutomatonBuilder[ConcreteAutomaton] {
  def getAutomaton = new ConcreteAutomaton
}

class UsesAutomataAsGraphs {
  val aut = new ConcreteAutomaton
  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:33:20: type mismatch;
[error]  found   : ConcreteAutomatonBuilder
[error]  required: AutomatonBuilder[ConcreteAutomaton.this.type]
[error] Note: ConcreteAutomaton >: ConcreteAutomaton.this.type (and ConcreteAutomatonBuilder <: AutomatonBuilder[ConcreteAutomaton]), but trait AutomatonBuilder is invariant in type A.
[error] You may wish to define A as -A instead. (SLS 4.5)
[error]   def getBuilder = new ConcreteAutomatonBuilder
[error]                    ^

遵循建议并制作 A 逆变器会给我带来其他问题。这也不是我真正想要的。我想要我的建造者生产完全相同类型的自动机。

【问题讨论】:

  • this.type 指的是当前实例的单例类型。您不能创建与当前实例相同类型的新实例。您可以使用 F-Boundedtypeclasses 解决您想做的事情,看看this
  • 谢谢;这就是我最初的想法,但我想我现在可能有一些可以工作的东西。如果我让它在真实世界的代码上工作,我会添加它作为回复。

标签: scala polymorphism builder


【解决方案1】:

我在这里添加另一个答案,因为它与另一个答案明显不同,而且它会变得太大。这个使用类型类和隐式类来做所有事情,我觉得它更安全,即使它看起来像样板,对你来说有点太多了。

不是将subgraph 放在Graphlike 特征中,而是不同的对象执行子图方法并构造新的自动机。隐式类提供了 subgraph 方法,因为我无法证明 GGraphlike 特征本身中 this 的类型。

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

trait Graphlike {
  type Vertex
}
trait Automaton extends Graphlike {
  type State
  type Vertex = State

  def states: Iterable[State]
  def initialState: State
}
class ConcreteAutomaton extends Automaton {
  type State = Int

  def states = List(1, 2, 3)
  def initialState = 1
}

trait AutomatonBuilder[+A <: Automaton] {
  def getAutomaton: A
}
class ConcreteAutomatonBuilder extends AutomatonBuilder[ConcreteAutomaton] {
  def getAutomaton = new ConcreteAutomaton
}


trait Subgraph[G <: Graphlike] {
  def subgraph(graph: G)(selectedVertices: Set[graph.Vertex]): G
}
trait AutomatonSubgraph[A <: Automaton] extends Subgraph[A] {
  protected def newBuilder: AutomatonBuilder[A]
  def subgraph(automaton: A)(selectedVertices: Set[automaton.Vertex]): A = {
    val builder = newBuilder
    //Do stuff to the builder here
    builder.getAutomaton
  }
}
implicit object ConcreteAutomatonSubgraph extends AutomatonSubgraph[ConcreteAutomaton] {
  protected def newBuilder = new ConcreteAutomatonBuilder()
}

implicit class Subgraphable[+G <: Graphlike](graph: G)(implicit sub: Subgraph[G]) {
  def subgraph(selectedVertices: Set[graph.Vertex]): G = sub.subgraph(graph)(selectedVertices)
}

class UsesAutomataAsGraphs {
  val aut = new ConcreteAutomaton
  val x = aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}

另一种更忠实于原始设计但我不喜欢的方法:https://scastie.scala-lang.org/AfKX5cpbSXqrqesT4rsUvA

【讨论】:

  • 谢谢!我会考虑这个以及我想做多少重构,但我看到了这个解决方案的优点。在整个重构过程中,我遇到的问题是,代码库的不同部分会以令人惊讶的方式与我所做的更改发生交互,而我根本无法预测。
  • 如果您愿意,请尝试第二种方式(链接在最底部)
  • 一个快速的后续问题:我是否需要为 Automaton 中的 every 方法添加双份?
  • 只有那些返回 A 或类似的东西。如果您有 union 方法或需要构建相同类型的自动机的东西,那么可以。否则,不。
【解决方案2】:

“构建this.type 的实例”听起来很奇怪。无论如何:

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
  def getBuilder: AutomatonBuilder[this.type]

  def subgraph(selectedVertices: Set[Vertex]): this.type = {
    val builder = getBuilder
    // Some logic to actually do something to the builder here
    builder.getAutomaton
  }
}

trait AutomatonBuilder[A <: Automaton] {
  def getAutomaton: A
}

class ConcreteAutomaton extends Automaton {
  type State = Int

  def states = List(1, 2, 3)
  def initialState = 1
  def getBuilder = new ConcreteAutomatonBuilder[this.type](this)   
}

class ConcreteAutomatonBuilder[A <: Automaton with Singleton](a: A) extends AutomatonBuilder[A] {
  def getAutomaton = a
}

Automaton 知道AutomatonBuilder 也很奇怪。

【讨论】:

  • 你会把建造者放在哪里?在Automaton 内部?基本上,我在现有的代码库中工作,当我到达那里时,设计的那部分就在那里,我只添加了图形特征。
  • 我认为构建器中的 getAutomaton 方法应该在设置完所有字段和内容后返回一个新的自动机,所以我不确定是否只返回用于构造它的相同自动机会工作的。
  • @user 这就是它的作用,是的。我只是将其用作示例的快速解决方法。
  • @albins 我只是在评论这里的构建器如何返回 A 而不是新的自动机
【解决方案3】:

按照 Dmytro Mitin 在另一个问题上的 answer,您可以通过使子图接受 Set[A#Vertex] 而不是 Set[this.Vertex] 来做到这一点。您不能像我在回答中建议的那样使用this.type,因为您从中获取子图的原始自动机可能会实现您的构建者不可能知道的各种东西。


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

  def subgraph(selectedVertices: Set[G#Vertex]): G
}

trait Automaton[A <: Automaton[A]] extends Graphlike[A] {
  type State
  type Vertex = State

  def getBuilder: AutomatonBuilder[A]
  def states: Iterable[State]
  def initialState: State

  def subgraph(selectedVertices: Set[A#Vertex]): A = {
    val builder = getBuilder
    // Some logic to actually do something to the builder here
    builder.addVertices(selectedVertices) //Example

    builder.getAutomaton
  }
}

trait AutomatonBuilder[A <: Automaton[A]] {
  def getAutomaton: A
  //example
  val verts = Set[A#Vertex]()
  def addVertices(s: Set[A#Vertex]): Unit = verts ++ s
}

class ConcreteAutomaton extends Automaton[ConcreteAutomaton] {
  type State = Int

  def states = List(1, 2, 3)
  def initialState = 1
  val getBuilder = new ConcreteAutomatonBuilder
}

class ConcreteAutomatonBuilder extends AutomatonBuilder[ConcreteAutomaton] {
  def getAutomaton = new ConcreteAutomaton
}

class UsesAutomataAsGraphs {
  val aut: ConcreteAutomaton = new ConcreteAutomaton
  aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-20
    • 2020-11-06
    • 1970-01-01
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 2021-02-12
    • 1970-01-01
    相关资源
    最近更新 更多