绘制一个可能的解决方案:
将地图操作放在辅助特征中
说GraphOps
(这可能是Graph
它本身,但地图签名可能太复杂了)
case class GraphOps[G](data: G) { def map...}
轻松获得GraphOps
:
object Graph {
def apply[G](data: G) = GraphOps(data)
}
这样,电话将是
Graph(set).map(f)
apply
可以隐含,但我不确定我想这样做(如果我这样做了,我不确定它会正确找到地图)。
变体。在 GraphOps 中有图表
我们也可以
case class GraphOps[G,V](data: G, graph: Graph[G,V])
和
object Graph {
def apply[G,V](data: G)(implicit graph: Graph[G,V]) = GraphOps(data, graph)
}
这样做的好处是顶点类型V
在 GraphOps 中可用
定义地图操作
您想要的签名很复杂,Set[(A,A)] 返回一个 Set[(B,B)],但其他图形实现返回完全不同的东西。这类似于在集合库中所做的。
我们可以引入一个特征 CanMapGraph[From, Elem, To],类似于 CanBuildFrom
trait CanMapGrap[FromGraph, FromElem, ToGraph, ToElem] {
def map(data: FromGraph, f: FromElem => ToElem): ToGraph
}
(您可能会将其更改为具有比 map 更多的基本操作,以便可以将其用于不同的操作,如使用 完成CanBuildFrom
)
然后地图将是
case class GraphOps[G](data: G) {
def map[A,B](f: A, B)(implicit ev: CanMapFrom[G, A, B, G2]) : G2 =
ev.map(data, f)
}
你可以定义
implicit def mapPairSetToPairSet[A, B] =
new CanMapGraph[Set[(A,A)], A, Set[(B,B)], B] {
def map(set: Set[(A,A)], f: A => B) = set.map{case (x, y) => (f(x), f(y))}
}
然后你做
val theGraph = Set("A" -> "B", "BB" -> "A", "B" -> "C", "C" -> "A")
Graph(theGraph).map(s: String -> s(0).toLower)
res1: Set[(Char, Char)] = Set((a,b), (b,a), (b,c), (c,a))
一个问题是顶点的类型在第一个参数列表中是未知的,即 f 的那个,所以我们必须明确地使用 s: String。
使用另一种方法GraphOps
,我们提前获取顶点类型,A
不是 Map 的参数,而是 的参数GraphOps
,因此从一开始就知道,不需要在 中显式f
。如果您这样做,您可能希望将图形传递给map
.CanMapGraph
使用第一个解决方案,仍然很容易将图形提供给CanMapGraph
.
implicit def anyGraphToSet[G,V,W](implicit graph: Graph[G,V])
= new CanMapFrom[G, V, Set[(W,W)], W] {
def map(data: G, f: V => W) =
(for {
from <- graph.nodes(data)
to <- graph.nodes(data))
if graph.adjacent(data, from, to) }
yield (from, to)).toSet
}