简体   繁体   中英

Building instances of `this.type` in Scala

I am working on a graph trait called Graphlike where I use dependent/associated types for the vertices. After solving my polymorphism issues , my implementation looks like this:

trait Graphlike {
  type Vertex

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

I also have various kinds of abstract atomic-state automata, which of course behave like graphs:

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
}

However, when I try to actually implement a concrete automaton, I run into trouble:

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))
}

gives:

[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]                    ^

Following the suggestion and making A contravariant gives me other issues. It's also not really what I want; I want my builders to produce the exact same type of automaton.

"Building instances of this.type " sounds strange. Anyway:

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
}

It's also strange that Automaton knows about AutomatonBuilder .

I'm adding another answer here because it's significantly different from the other one and because it would get too big. This one uses typeclasses and implicit classes to do everything, and I feel it's safer, even though it may seem boilerplate-y and a little too much to you.

Instead of putting subgraph in the Graphlike trait, different objects do the subgraph method and construct new automatons. An implicit class provides the subgraph method because I couldn't prove that G was the type of this in the Graphlike trait itself.

Scastie: 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))
}

Another approach that stays more faithful to your original design but I don't like: https://scastie.scala-lang.org/AfKX5cpbSXqrqesT4rsUvA

Following Dmytro Mitin's answer on the other question, you can do this, by making subgraph accept Set[A#Vertex] instead of Set[this.Vertex] . You can't use this.type as I suggested in my answer there because your original automaton that you're getting a subgraph from may implement all kinds of stuff that your builder can't possibly know about.


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))
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM