It is sometimes useful to be able to manipulate a Dot graph as an
actual graph. This representation lets you do so, using an
inductive approach based upon that from FGL (note that DotGraph
is not an instance of the FGL classes due to having the wrong
kind). Note, however, that the API is not as complete as proper
graph implementations.

For purposes of manipulation, all edges are found in the root graph
and not in a cluster; as such, having EdgeAttrs in a cluster's
GlobalAttributes is redundant.

This representation doesn't allow non-cluster sub-graphs. Also, all
clusters must have a unique identifier. For those functions (with
the exception of DotRepr methods) that take or return a "Maybe
GraphID", a value of "Nothing" refers to the root graph; "Just
clust" refers to the cluster with the identifier "clust".

You would not typically explicitly create these values, instead
converting existing Dot graphs (via fromDotRepr). However, one
way of constructing the sample graph would be:

A polymorphic type that covers all possible ID values allowed by
Dot syntax. Note that whilst the ParseDot and PrintDot
instances for String will properly take care of the special
cases for numbers, they are treated differently here.

The graphToDot function from Data.GraphViz produces output
suitable for this function (assuming all clusters are provided
with a unique identifier); graphElemsToDot is suitable if all
nodes are specified in the input list (rather than just the
edges).

Graph construction

Merge the Context into the graph. Assumes that the specified
node is not in the graph but that all endpoints in the
successors and predecessors (with the exception of loops)
are. If the cluster is not present in the graph, then it will be
added with no attributes with a parent of the root graph.

Note that & and decompose are not quite inverses, as this
function will add in the cluster if it does not yet exist in the
graph, but decompose will not delete it.

Add a new cluster to the graph; throws an error if the cluster
already exists. Assumes that it doesn't match the identifier of
the overall graph. If the parent cluster doesn't already exist
in the graph then it will be added.