【问题标题】:Scala: value class X is added to the return type of its methods as X#Scala:值类 X 添加到其方法的返回类型为 X#
【发布时间】:2019-04-19 13:14:41
【问题描述】:

我想丰富一个“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)
        ...
    }
...

问题在于其方法的返回类型,例如:

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! 

原因是否在于“Graph”具有依赖的子类型,例如节点T?有没有办法让这种充实发挥作用?

【问题讨论】:

    标签: scala enrich-my-library


    【解决方案1】:

    通常有效的方法是将单例类型作为类型参数传播到EnrichGraph。这意味着一些额外的样板,因为您必须将 implicit class 拆分为 classimplicit 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] } 类型,它看起来很复杂。但重要的部分是这种类型仍然包含_1.G.NodeT 中的G 具有H.type 类型的知识,因为该信息存储在val _1: EnrichGraph[H.type] 中。

    对于Set,您不能使用G#NodeT 为您提供更简单的类型签名,因为G.NodeTG#NodeT 的子类型,而Set 不幸的是不变的。在我们的使用中,这些类型实际上总是等价的(正如我上面解释的),但编译器无法知道这一点。

    【讨论】:

    • 或许,应该是class EnrichGraph[G &lt;: Graph](val G: G) extends AnyVal { def roots: scala.collection.mutable.Set[G.NodeT] = G.nodes.filter(!_.hasPredecessors) }
    • 应该是G#NodeT。 OP 获得这些奇怪类型的原因是 val G 绑定到 EnrichGraph 的实例,但该实例不可用于依赖路径的类型。如果我猜对了这里使用的库,你可能是对的 Set,但是 OP 说他想要一个 Iterable,所以你去。并且Set 扩展了Iterable,所以它确实有效。不幸的是,这个问题不是独立的,所以使用 is 提供的东西似乎最好:)
    • 使用G#NodeT,代码无法在Scala 2.12.8 + graph-core 1.12.5 gist.github.com/DmytroMitin/50faeb1f92011df664d168ffc3c26874 中编译,错误为type mismatch; [error] found : scala.collection.mutable.Set[EnrichGraph.this.G.NodeT] [error] required: scala.collection.mutable.Set[G#NodeT] [error] Note: EnrichGraph.this.G.NodeT &lt;: G#NodeT, but trait Set is invariant in type A. [error] You may wish to investigate a wildcard type such as `_ &lt;: G#NodeT`. (SLS 3.2.10)
    • 这让我们回到了Iterable[G#NodeT],我猜。或者像错误提示的那样使用Set[_ &lt;: G#NodeT]
    • Iterable[G#NodeT] 错误为 type mismatch; [error] found : Iterable[App.H.NodeT] [error] required: App.H.NodeFilter [error] (which expands to) App.H.NodeT =&gt; Boolean 。对于scala.collection.mutable.Set[_ &lt;: G#NodeT],错误为type mismatch; [error] found : scala.collection.mutable.Set[_$1] where type _$1 &lt;: App.H.NodeT [error] required: App.H.NodeFilter [error] (which expands to) App.H.NodeT =&gt; Boolean
    猜你喜欢
    • 2022-12-30
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多