1

I'm struggling to understand how simple graphs are represented in Haskell. My understanding is that a graph would basically be a list of vertices and edges (pairs of vertices)

I've been looking at the Data.Graph implementation in order to build a Graph constructor with a type variable "a" for a simple graph, but I don't understand what the constructor should look like and how it will store an Edge and a Vertex.

My initial thinking was to base my constructor on a similar logic as a tree:

type Vertex = Int
type Edge = (Vertex, Vertex)
data Graph a = Void | Vertex a [Graph a]

But I'm not sure how the edges are then represented.

desertnaut
  • 57,590
  • 26
  • 140
  • 166
AwhatLoop
  • 31
  • 4
  • 1
    See also [How do you represent a graph in Haskell?](https://stackoverflow.com/q/9732084/791604). Perhaps this is even a duplicate of that, though I'm mildly uncertain because this one appears to be asking about fixing a specific chunk of code. – Daniel Wagner Oct 14 '21 at 13:53

1 Answers1

3

It's tempting to try to represent a graph structurally in Haskell ADTs, but this doesn't really work the way it does with trees because of the presence of loops. It would be possible to represent only a spanning tree, but then the remaining edges need to be represented as addresses into the tree, which is possible but awkward and if you need direct addressing anyway, what's the point of having the tree structure at all? That's why the standard way is to instead just flatten it completely, an array of vertices and a list (or another array) of edges.

(The vertices can't reasonably be stored in a list because that would have too slow direct access.)

If you want to add extra data, like you would add data to list nodes, you can just add them to the vertex data.

{-# LANGUAGE DeriveFunctor #-}

import Data.Vector as V

newtype Vertex a = Vertex { getVertexData :: a }
 deriving (Functor, Eq, Show)
type VertexIndex = Int
type Edge = (VertexIndex, VertexIndex)

data Graph a = Graph
  { graphVertices :: V.Vector (Vertex a)
  , graphEdges :: V.Vector Edge
  } deriving (Functor, Show)

IMO there are actually valid reasons to want a tree structure, including to support lazyness. This could be used in some kind of comonadic interface; I dabbled with that once, not sure if somebody has done it properly somewhere.


Just for fun, here is a simple (and inefficient) implementation of nonempty connected graphs based on a spanning tree.

data TreeAddress = Here
                 | Up TreeAddress
                 | Down Int TreeAddress

data ConnectedGraph a = Vertex
        { vertexContainedData :: a
        , managedNeighbours :: [ConnectedGraph a]
        , unmanagedNeighbours :: [TreeAddress]
        }

To make it a bit less wasteful, TreeAddress could be condensed down into a single Int if we also keep track of the total number of vertices, or at least Integer if we quotient out the number of managed neighbours at each junction.

It would be a fun exercise to write a Comonad instance for this.

Ah, somebody seems to have done this in Scala.

And they use a library that was itself inspired by the Haskell fgl library! I knew somebody had to have done this already. In fact it's quite old.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • You can tie the knot with `data Vertex a = Vertex a [Vertex a]` and then e.g. `a = Vertex 'a' [b,c]; b = Vertex 'b' [c, a]; c = Vertex 'c' [a, b]`. Not that it's easy to work with this kind of data structure. – n. m. could be an AI Oct 14 '21 at 17:00
  • 1
    @n.1.8e9-where's-my-sharem. that doesn't work, a cyclic graph would be indistinguishable from an infinitely deep linear one. – leftaroundabout Oct 14 '21 at 17:09
  • It is sometimes indistinguishable in real life too (e.g. one can get lost in a labyrinth). One needs to label vertices with unique IDs. – n. m. could be an AI Oct 14 '21 at 19:14