使用类型类时,如何以不同的方式处理对象?

时间:2011-10-25 10:53:28

标签: scala types functional-programming scalaz

假设我有一个类型类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)]

map上已定义了Set。如何以不同的方式处理相同数据结构被视为同一事物(具有map函数的东西)的问题?

3 个答案:

答案 0 :(得分:3)

草拟可能的解决方案:

将地图操作放在辅助特征

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))

一个问题是顶点的类型在第一个参数列表中是未知的,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
  }

答案 1 :(得分:1)

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]) = ...
}

答案 2 :(得分:0)

如果您正在使用类型类,那么您可以执行以下操作:

implicitly[TypeClass].map(...)

如果您正在使用视图边界,那么Alexey的答案是正确的:

(...: ViewBound).map(...)