Draw a possible solution:
Put the mapping operation in an auxiliary characteristic
say GraphOps (it could be Graph , but maybe the cartographic signature would be too complicated)
case class GraphOps[G](data: G) { def map...}
Easy to get GraphOps :
object Graph { def apply[G](data: G) = GraphOps(data) }
In this case, the call will be
Graph(set).map(f)
apply may be implicit, but I'm not sure I want to do this (and if I did, Iβm not sure that it will find the map properly).
Option. Have a graph in GraphOps
we can also do
case class GraphOps[G,V](data: G, graph: Graph[G,V])
and
object Graph { def apply[G,V](data: G)(implicit graph: Graph[G,V]) = GraphOps(data, graph) }
A good point is that the vertex type V is available in GraphOps
Display operation definition
Your signature is complex: Set [(A, A)] returns Set [(B, B)], but other graph implementations return something completely different. This is similar to what is done in the collection library.
We can introduce the sign CanMapGraph [From, Elem, To], akin to CanBuildFrom
trait CanMapGrap[FromGraph, FromElem, ToGraph, ToElem] { def map(data: FromGraph, f: FromElem => ToElem): ToGraph }
(perhaps you would change this to more basic operations than on the map so that it can be used for different operations, as is done with CanBuildFrom )
Then the mapping will be
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) }
You can define
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))} }
And then you do
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))
The problem is that the type of the vertices is unknown in the first argument list, one for f, so we have to be explicit with s: String.
With an alternative to GraphOps , where we get an early vertex type, A not a Map parameter, but a GraphOps , so it is known from the very beginning and does not have to be explicit in f . You do this so you can pass the graph to the map method in CanMapGraph .
With the first solution, it is still easy to give a CanMapGraph chart.
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 }