Scala: значение класса X добавляется к возвращаемому типу его методов как X # - PullRequest
3 голосов
/ 19 апреля 2019

Я бы хотел обогатить график 'graph for scala'. Для этой цели я создал класс неявных значений:

import scalax.collection.mutable
import scalax.collection.edge.DiEdge

...
    type Graph = mutable.Graph[Int, DiEdge]
    implicit class EnrichGraph(val G: Graph) extends AnyVal {
        def roots = G.nodes.filter(!_.hasPredecessors)
        ...
    }
...

Проблема заключается в типе возвращаемого значения его методов, например ::1004

import ....EnrichGraph

val H: Graph = mutable.Graph[Int,DiEdge]()

val roots1 = H.nodes.filter(!_.hasPredecessors)  // type Iterable[H.NodeT]
val roots2 = H.roots        // type Iterable[RichGraph#G.NodeT] !!

val subgraph1 = H.filter(H.having(roots1)) // works!
val subgraph2 = H.filter(H.having(roots2)) // type mismatch! 

Причина в том, что у «Графа» есть зависимые подтипы, например, NODET? Есть ли способ заставить это обогащение работать?

1 Ответ

4 голосов
/ 19 апреля 2019

Обычно работает передача одиночного типа в качестве параметра типа в 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, к сожалению, инвариантен. При нашем использовании эти типы всегда будут эквивалентны (как я объяснил выше), но компилятор не может этого знать.

...