Обычно работает передача одиночного типа в качестве параметра типа в EnrichGraph
. Это означает, что вам нужно немного больше, так как вам нужно разделить implicit class
на class
и implicit def
.
class EnrichGraph[G <: Graph](val G: G) extends AnyVal {
def roots: Iterable[G#NodeT] = G.nodes.filter(!_.hasPredecessors)
//...
}
implicit def EnrichGraph(g: Graph): EnrichGraph[g.type] = new EnrichGraph[g.type](g)
Суть здесь в том, что G#NodeT =:= H.NodeT
, если G =:= H.type
, или, другими словами, (H.type)#NodeT =:= H.NodeT
. (=:=
является оператором равенства типов)
Причина, по которой вы получили этот странный тип, заключается в том, что roots
имеет тип, зависящий от типа пути. И этот путь содержит значение G
. Тогда тип val roots2
в вашей программе должен содержать путь к G
. Но поскольку G
связан с экземпляром EnrichGraph
, на который не ссылается ни одна переменная, компилятор не может создать такой путь. «Лучшая» вещь, которую может сделать компилятор, - это создать тип с этой частью пути: Set[_1.G.NodeT] forSome { val _1: EnrichGraph }
. Это тип, который я на самом деле получил с вашим кодом; Я предполагаю, что вы используете Intellij, который печатает этот тип по-другому.
Как указывает @DmytroMitin, версия, которая может работать лучше для вас:
import scala.collection.mutable.Set
class EnrichGraph[G <: Graph](val G: G) extends AnyVal {
def roots: Set[G.NodeT] = G.nodes.filter(!_.hasPredecessors)
//...
}
implicit def EnrichGraph(g: Graph): EnrichGraph[g.type] = new EnrichGraph[g.type](g)
Поскольку для остальной части кода требуется Set
вместо Iterable
.
Причина, по которой это все еще работает, несмотря на повторное введение зависимого от пути типа, довольно хитрая. На самом деле теперь roots2
получит тип Set[_1.G.NodeT] forSome { val _1: EnrichGraph[H.type] }
, который выглядит довольно сложным. Но важная часть заключается в том, что этот тип по-прежнему содержит сведения о том, что G
в _1.G.NodeT
имеет тип H.type
, поскольку эта информация хранится в val _1: EnrichGraph[H.type]
.
С Set
вы не можете использовать G#NodeT
, чтобы дать вам более простые подписи типов, потому что G.NodeT
является подтипом G#NodeT
, а Set
, к сожалению, инвариантен. При нашем использовании эти типы всегда будут эквивалентны (как я объяснил выше), но компилятор не может этого знать.