【问题标题】:When using type classes, how to deal with object in different ways?使用类型类时,如何以不同的方式处理对象?
【发布时间】:2011-12-14 20:21:10
【问题描述】:

假设我有一个类型类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函数的事物)的问题?

【问题讨论】:

  • 你是如何定义类型类的?据我了解,您需要为要制作成图形的每个类创建一个 Graph[G,V] 实例,如 SetIsGraphable extends Graphable[Set[(V,V)], V] .但是由于 Set 是参数化的,你肯定需要为每个 V 提供一个实例。
  • @Submonoid 这是正确的,我已经添加了隐含的澄清。
  • 您是否考虑过拥有Set 然后在其上调用map 并让它做一些不是 Set 的地图实现(或子类实现)可能是个坏主意?如果你想做一些不同的事情,你必须以某种方式告诉编译器你想要一些不同的东西;最简单的方法是明确使用Graph 而不是Set。
  • @Ben 我知道这是个问题。我想知道一些处理这种困境的好方法。

标签: scala types functional-programming scalaz


【解决方案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]) = ...
}

【讨论】:

  • 但是Graph是一个类型类,x没有转换成一个。
  • 这行得通,我喜欢您将G 参数化的建议。但是我无法通过简单地提供一个图形被指向的证据来使用Scalaz提供的所有功能;它提供了地图等等。我看到这不能以某种方式工作,因为已经为 Set 提供了隐式类型类对象。但我希望收到有关如何解决此问题的意见。
【解决方案2】:

草拟一个可能的解决方案:

将 map 操作放在辅助 trait 中

说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的参数,所以从一开始就知道,不需要在@中显式987654339@。如果您这样做,您可能希望将图形传递给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
  }

【讨论】:

  • 很好的答案。如果你想创建一个修改过的对象,我暂时忘记了提供构建器的需要。所以基本上,解决方案是包装对象并附加有关包装对象如何实现各种“功能”的信息。
【解决方案3】:

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

implicitly[TypeClass].map(...)

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 2013-08-14
    • 1970-01-01
    • 2021-10-16
    • 2012-02-14
    • 1970-01-01
    • 2010-10-07
    相关资源
    最近更新 更多