A Practical Approach to Graph Manipulation
by JeanPhilippeBernardy for The Monad.Reader Issue 5
2005-07-08
Abstract.
Tree-based data structures are easy to deal with in haskell. However, working with graph-like structures in practice is much less obvious. In this article I present a solution that has worked for me in many cases.
1. Introduction
I always found that dealing with graphs in haskell is a tricky subject. Even something like a implementing a depth-first search, which is trivally achieved in an imperative language, deserves an article on its own for haskell [4]. A PhD thesis has been written on the subject of graphs and functional programming [2], and it seems that it still doesn't exhaust the design-space: radically different ideas have been proposed afterwards [3].
In this article I'll present (a simplified version of) a solution that I think deserves more coverage [1]. The idea is to abstract graph manipulation by anamorphisms and catamorphisms. This approch features "separation of concerns" and "composability", hence it can be readily applied to practical problems.
-
Section 2 shows how anamorphisms and catamorphisms can be generalised to graphs.
-
Section 3 details the data structures used to represent graphs
-
Section 4 discusses various problems where cata/anamorphisms can be applied
-
Section 5 gives a sample implementation for the catamorphism and anamorphism
-
Section 6 concludes
1.1. Nota
This article has been generated from a literate haskell source. So, although the text of this wiki page will not compile, all the examples are real and run. The source can be accessed here: PracticalGraphHandling.lhs
We will assume you know the hierarchcal libraries. Refer to http://haskell.org/ghc/docs/latest/html/libraries/index.html in case of doubt.
2. Origami with Graphs
2.1. Fold & Unfold (the big deal)
Most of you probably know what a "fold" (also known as catamorphism) is. For those who don't, intuitively, it's an higher-order operation that reduces a complex structure to a single value. It applies a function given as parameter to each node, propagating the results up to the root. This is a highly imprecise definition, for more details please read [5].
For example, the fold operation on lists can be typed as follows:
foldr :: (a -> b -> b) -> -- ^ operation to apply
b -> -- ^ initial value
[a] -> -- ^ input list
b -- ^ result
