При использовании классов типов, как обращаться с объектом по-разному? - PullRequest
7 голосов
/ 25 октября 2011

Предположим, у меня есть класс типа 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) по-разному?

Ответы [ 3 ]

3 голосов
/ 25 октября 2011

Эскиз возможного решения:

Поместите операцию карты во вспомогательную черту

скажем 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)

Тогда map будет

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 голос
/ 25 октября 2011
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]) = ...
}
0 голосов
/ 25 октября 2011

Если вы используете классы типов, то вы можете сделать что-то вроде этого:

implicitly[TypeClass].map(...)

Если вы используете границы представления, тогда Алексей ответит правильно:

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