I have a trait called Graphlike for things that work as a graph. Notably, one of the properties I want to have is that the method g.subgraph(Set(1, 2, 3)) would return a subgraph of the same type with just the vertices 1, 2 and 3. Apparently, this means that I want F-bounded polymorphism and that Graphlike looks something like this:
trait Graphlike[A <: Graphlike[A]] {
type Vertex
def subgraph(selectedVertices: Set[Vertex]): A
}
I also have a trait representing an automaton with associated types for edges and vertices. I want it to behave like a graph. Simplified, it looks like this:
trait Automaton extends Graphlike[Automaton] {
type State
type Vertex = State
def states: Iterable[State]
def initialState: State
}
This almost works. However, Scala's type system gets confused when I try to mix the two and do something useful with the result:
class UsesAutomataAsGraphs(val aut: Automaton with Graphlike[Automaton]) {
aut.subgraph(Set(aut.initialState)).subgraph(Set(aut.initialState))
}
gives an error like:
[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))
How do I get Scala to understand that the two associated types are the same in all derived objects?
The easiest solution seems to be this. Just make sure that subgraph returns the same type with
this.typeand you're good to go. There's no need forA- it just adds additional complexity as you try to prove thatAis the type ofthis.In Scastie: https://scastie.scala-lang.org/zMtde7VISKi18LdPXO6Ytw
Having
Statebe a type parameter also worked for me. Note that inUsesAutomataAsGraphs, if you useA <: Automaton[_](a wildcard), it doesn't work, becauseStatecould be anything. The compiler wants your assurance that the returnedAutomatonwill have the sameStatetype (because it's unbounded, and other classes extendingAutomatoncould define it differently).Link to Scastie: https://scastie.scala-lang.org/RolPc3ggTxeZ2tUqdXKNEQ
It also works if you define
subgraphas such:Because it's contravariant, even if
Vertexis different in different classes and/or traits, it'll work.Link to Scastie: https://scastie.scala-lang.org/fz509HEpTBGoJGaJxLziBQ