在使用类型类时,如何以不同的方式处理对象?
假设我有一个类型为Graph[G,V]
类Graph[G,V]
它指出G
类型的对象也是一个顶点类型为V
。
现在我有一个隐含的表达式,它让我把A
型对的集合看作一个顶点类型为A
(不能表示不连接的顶点......)。 我可以通过导入下列对象的作用域来使用隐式。
object TupleSetGraph{
implicit def ts2graph[A]: Graph[Set[(A,A)],A] = new Graph[Set[(A,A)],A] {
def nodes(g: Set[(A, A)]): Set[A] = g flatMap (t => Set(t._1,t._2))
def adjacent(g: Set[(A, A)], n1: A, n2: A): Boolean = g.contains((n1,n2)) || g.contains((n2,n1))
}
}
假设我也希望能够映射顶点的内容,从而能够执行以下操作:
(_: Set[(A,A)]).map((_: A => B)): Set[(B,B)]
但是在Set
上已经定义了一张map
。 如何处理相同的数据结构可以以不同的方式被看作是同一事物(具有map
函数的东西)的问题?
草绘一个可能的解决方案:
将地图操作放在辅助特征中
说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)
}
最好的一点是,在GraphOps中可以使用顶点类型V
定义地图操作
你想要的签名是复杂的,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))
一个问题是,顶点的类型在第一个参数列表中是不知道的,因为我们必须用s:String来显式表示顶点的类型。
使用替代的GraphOps
,我们可以尽早得到顶点类型, A
不是Map的参数,而是GraphOps
的参数,所以它从一开始就是已知的,并且不需要在f
是显式的。 你这样做,你可能想要将图传给CanMapGraph
方法map
。
使用第一种解决方案,将图形交给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
}
val x: Set[(A, A)] = ...
(x: Graph[_, _]).map(...)
如果你想让名字相同,似乎是你能做的最好的。
正如你指出的那样,这不是你想要的。 这应该会更好:
object Graph {
def map[G, V](graph: G)(f: V => V)(implicit instance: Graph[G, V]) = ...
}
val x: Set[(A, A)] = ...
Graph.map(x)(f)
// but note that the type of argument of f will often need to be explicit, because
// type inference only goes from left to right, and implicit arguments come last
请注意,您只能让f
为V => V
而不是V => V1
。 为什么? 设想你有implicit g1: Graph[SomeType, Int]
,但不是implicit g2: Graph[SomeType, String]
。 什么Graph.map(_: SomeType)((_: Int).toString)
然后返回? 通过要求G
成为参数化类型可以避免此问题:
trait Graph[G[_]] {
def nodes[A](g: G[A]): Set[A]
def adjacent[A](g: G[A], n1: A, n2: A): Boolean
}
object TupleSetGraph{
type SetOfPairs[A] = Set[(A,A)]
implicit def ts2graph: Graph[SetOfPairs] = new Graph[SetOfPairs] {
def nodes[A](g: Set[(A, A)]): Set[A] = g flatMap (t => Set(t._1,t._2))
def adjacent[A](g: Set[(A, A)], n1: A, n2: A): Boolean = g.contains((n1,n2)) || g.contains((n2,n1))
}
}
那么你有
object Graph {
def map[G[_], V, V1](graph: G[V])(f: V => V1)(implicit instance: Graph[G]) = ...
}
如果你使用的是类型类,那么你可以这样做:
implicitly[TypeClass].map(...)
如果你使用视图边界,那么Alexey的答案是正确的:
(...: ViewBound).map(...)
链接地址: http://www.djcxy.com/p/55753.html
上一篇: When using type classes, how to deal with object in different ways?