Copyright (C) 2017, H. Conrad Cunningham
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of October 2017 is a recent version of Firefox from Mozilla.
TODO:
In this case study, we seek to develop a family of graph data structures that can support the implementation of Adventure games similar to the Wizard's Adventure game given in Chapter 5 of the book Land of Lisp: Learn to Program in Lisp, One Game at a Time [Barski 2011]. We develop this family as an abstract data type and implement it as modules in Haskell.
This abstract data type represents doubly labeled directed graphs. A directed graph (digraph) includes vertices (nodes) and edges directed from one node to another. The graph allows arbitrary data (i.e., labels) to be attached to both vertices and edges in the directed graph.
Note: In this case study we use the acronym ADT to refer to an abstract data type. We also use an algebraic data type to denote the graph type provided by a Haskell module and perhaps use other algebraic data types in implementing the module.
The objectives of this case study are to:
specify a Labeled Digraph abstract data type using a constructive model (sets and interface invariant for the, preconditions/postconditions for each operation)
implement the abstract data type as a module in Haskell with at least two distinct implementations, giving appropriate implementation invariants for each
One source of information on digraphs and their specification we referenced is Chapter 10 of the book Abstract Data Types: Specifications, Implementations, and Applications [Dale-Walker 1996].
We use the following notation and terminology to describe the abstract data type's model and its semantics. (We choose notation that can readily be used in comments in the Haskell program.)
(ForAll x, y :: p(x,y)) is true if and only if predicate p(x,y) is true for all values of x and y.
(Exists x, y :: p(x,y)) is true if and only if there is at least one pair of values x and y for which p(x,y) is true.
(# x, y :: p(x,y)) yields a count of pairs (x,y) for which p(x,y) is true.
<=> denotes logical equivalence. p <=> q is true if and only if the logical (Boolean) values p and q are equal (i.e., both true or both false).
x IN C is true if and only if value x is member of a collection C (such as a set, bag, or sequence). Similarly, x NOT_IN C denotes the negation of x IN C.
A type consists of a set of values and a set of operations. We sometimes say a value is IN a type to mean the value is IN the set associated with the type.
For sets C and D, C UNION D denotes set union, that is, a set that includes all the element of both C and D.
For sets C and D, C INTERSECT D denotes set intersection, that is, a set that includes all elements that are both in C and in D.
For sets C and D, C - D denotes set difference, that is, the set C with all elements of set D removed.
For sets C and D, C SUBSET_OF D denotes that C is subset of D, that is, all the elements of C also occur in D.
A tuple such as (x,*) appearing in a collection such as { (x,*) } denotes element x grouped with all possible values of the second component. Note: We could also write { (x,*) } using q quantification as:
{ (x,c) :: c IN some_domain }A function is a special case of a relation and relation is a set of ordered pairs (tuples). We sometimes manipulate functions or relations using set notation for convenience.
A total function is defined for all elements of its domain. A partial function is defined for a subset of the elements of its domain.
We parameterize the abstract model for the Labeled Digraph ADT with the following types.
VertexType is the set of possible vertices (i.e., vertex identifiers).
VertexLabelType is the set of possible labels on vertices. (Values of this type may have several components.)
EdgeLabelType is the set of possible labels on edges. (Values of this type may have several components.)
We model the state of the instance of the Labeled Digraph ADT with an abstract value G such that G = (V,E,VL,EL) with G's components satisfying the following Labeled Digraph Properties.
V is a finite subset of values from the set VertexType. V denotes the vertices (or nodes) of the digraph.
E is a binary relation on the set V. A pair (v1,v2) IN E denotes that there is a directed edge from v1 to v2 in the digraph.
Note that this model allows at most one (directed) edge from a vertex v1 to vertex v2.
VL is a total function from set V to the set VertexLabelType.
EL is a total function from set E to the set EdgeLabelType.
We define the following interface invariant for the Labeled Digraph ADT:
Any valid labeled digraph instance
G, appearing in either the arguments or return value of a public ADT operation, must satisfy the Labeled Digraph Properties.
We specify the various ADT operations below using their type signatures, preconditions, and postconditions. Along with the interface invariant, these comprise the (implementation-independent) specification of the ADT.
In these assertions, for a digraph g that satisfies the invariants, G(g) denotes its abstract model(V,E,VL,EL) as described above. The value Result denotes the return value of function.
Constructor new_graph creates and returns a new instance of the graph ADT.
Precondition:
TruePostcondition:
G(Result) == ({},{},{},{})Accessor is_empty g returns true if and only if graph g is empty.
Precondition:
G(g) = (V,E,VL,EL)Postcondition:
Result == (V == {} && E == {})Mutator add_vertex g nv nl inserts vertex nv with label nl into graph g and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) && nv NOT_IN VPostcondition:
G(Result) == (V UNION {nv}, E, VL UNION {(nv,nl)}, EL)Mutator remove_vertex g ov deletes vertex ov from graph g and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) && ov IN VPostcondition:
G(Result) == (V', E', VL', EL')
where V' = V - {ov}
E' = E - {(ov,*),(*,ov)}
VL' = VL - {(ov,*)}
EL' = EL - {((ov,*),*),((*,ov),*)}Mutator update_vertex g ov nl changes the label on vertex ov in graph g to be nl and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) && ov IN V Postcondition:
G(Result) == (V - {ov}, E, VL', EL)
where VL' = (VL - {(ov,VL(ov))}) UNION {(ov,nl)}Accessor get_vertex g ov returns the label from vertex ov in graph g
Precondition:
G(g) = (V,E,VL,EL) && ov IN V Postcondition:
Result == VL(ov) Accessor has_vertex g ov returns true if and only if ov is a vertex of graph g.
Precondition:
G(g) = (V,E,VL,EL) && ov IN VertexLabelType Postcondition:
G(Result) == ov IN V Mutator add_edge g v1 v2 nl inserts an edge from vertex v1 to vertex v2 in graph g and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) && v1 IN V && v2 IN V &&
(v1,v2) NOT_IN EPostcondition:
G(Result) == (V, E', VL, EL')
where E' = E UNION {(v1,v2)}
EL' = EL UNION {((v1,v2),nl)}Mutator remove_edge g v1 v2 deletes the edge from vertex v1 to vertex v2 from graph g and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) V - {ov} && (v1,v2) IN EPostcondition:
G(Result) == (V, E - {(v1,v2)}, VL, EL - { ((v1,v2),*) }Mutator update_edge g v1 v2 nl changes the label on the edge from vertex v1 to vertex v2 in graph g to have label nl and returns the resulting graph.
Precondition:
G(g) = (V,E,VL,EL) && (v1,v2) IN EPostcondition:
G(Result) == (V, E, VL, EL')
where EL' == (EL - {((v1,v2),*)}) UNION {((v2,v2),nl) Accessor get_edge g v1 v2 returns the label on the edge from vertex v1 to vertex v2 in graph g.
Precondition:
G(g) = (V,E,VL,EL) && (v1,v2) IN EPostcondition:
Result == EL((v1,v2)) Accessor has_edge g v1 v2 returns true if and only if there is an edge from a vertex v1 to a vertex v2 in graph g.
Precondition:
G(g) = (V,E,VL,EL)Postcondition:
Result == (v1,v2) IN E Accessor all_vertices g returns a sequence of all the vertices in graph g. The returned sequence is represented by a builtin Haskell list.
Precondition:
G(g) = (V,E,VL,EL) Postcondition:
(ForAll ov: ov IN Result <=> ov IN V) &&
length(Result) == size(V) Accessor from_edges g v1 returns a sequence of all vertices v2 such that there is an edge from vertex v1 to vertex v2 in graph g. The returned sequence is represented by a builtin Haskell list.
Precondition:
G(g) = (V,E,VL,EL) && v1 IN V Postcondition:
(ForAll v2: v2 IN Result <=> (v1,v2) IN E) &&
length(Result) == (# v2 :: (v1,v2) IN E) Function from_edges g v1 should return [] when v1 does not appear in g, so that it can work well with the Wizard's Adventure game.
Accessor all_vertices_labels g returns a sequence of all pairs (v,l) such that v is a vertex and l is it's label in graph g. The returned sequence is represented by a builtin Haskell list.
Precondition:
G(g) = (V,E,VL,EL)Postcondition:
(ForAll v, l: (v,l) IN Result <=> (v,l) IN VL) &&
length(Result) == size(VL) Accessor from_edges_labels g v1 returns a sequence of all pairs (v2,l) such that there is an edge (v1,v2) labeled with l in graph g.
Precondition:
G(g) = (V,E,VL,EL) && v1 IN VPostcondition:
(ForAll v2, l :: (v2,l) IN Result <=> ((v1,v2),l) IN EL) &&
length(Result) == (# v2 :: (v1,v2 ) IN E) Function from_edges_labels g v1 should return [] when v1 does not appear in g, so that it can work well with the Wizard's Adventure game.
Below we state the header for a Haskell module Digraph_XXX that implements the Labeled Digraph ADT. The module name suffix XXX denotes the particular implementation for a data representation, but the signatures and semantics of the operations are the same regardless of representation.
The module exports data type Digraph, but its constructors are not exported. This allows modules that import Digraph_XXX to use the data type without revealing how the data type is implemented. (If we had Digraph(..) in the export list, then the data type and all its constructors would be exported.)
module DigraphADT_XXX
( Digraph -- constrain ops (Eq a, Show a, Show b, Show c)
, new_graph -- Digraph a b c
, is_empty -- Digraph a b c -> Bool
, add_vertex -- Digraph a b c -> a -> b -> Digraph a b c
, remove_vertex -- Digraph a b c -> a -> Digraph a b c
, update_vertex -- Digraph a b c -> a -> b -> Digraph a b c
, get_vertex -- Digraph a b c -> a -> b
, has_vertex -- Digraph a b c -> a -> Bool
, add_edge -- Digraph a b c -> a -> a -> c -> Digraph a b c
, remove_edge -- Digraph a b c -> a -> a -> Digraph a b c
, update_edge -- Digraph a b c -> a -> a -> c -> Digraph a b c
, get_edge -- Digraph a b c -> a -> a -> c
, has_edge -- Digraph a b c -> a -> a -> Bool
, all_vertices -- Digraph a b c -> [a]
, from_edges -- Digraph a b c -> a -> [a]
, all_vertices_labels -- Digraph a b c -> [(a,b)]
, from_edges_labels -- Digraph a b c -> a -> [(a,c)]
)
where -- definitions for the types and functionsNote: The Glasgow Haskell Compiler (GHC) release 8.2 (July 2017) and the Cabal-Install package manager release 2.0 (August 2017) support a new mixin package system called Backpack. This extension would enable us to define an abstract module "DigraphADT" as a signature file with the above interface. Other modules can then implement this abstract interface thus giving a more explicit and flexible definition of this abstract data type.
The Haskell List representation uses the following values for the type parameters:
VertexType is an instance of Haskell classes Eq and Show (i.e., can be compared for equality and converted to strings),
VertexLabelType is an instance of Haskell class Show,
EdgeLabelType is an instance of Haskell class Show.
That is, vertices can be compared for equality. Vertices and both the vertex and edge labels can be displayed as strings.
Note: It may be desirable to require VertexType to be from class Ord (totally ordered) and VertexLabelType and EdgeLabelType to be from class Eq. These were not necessary for the List implementation, but were necessary for the Map implementation.
The List implementation represents a labeled digraph as an instance of the Haskell algebraic data type Digraph, in particular data constructor (Graph vs es).
In an instance (Graph vs es):
vs is a list of tuples (v,vl) where
v has VertexType and represents a vertex of the digraphvl has VertexLabelType and is the unique label associated with vertex vv occurs at most once in vs (i.e., vs encodes a function from vertices to vertex labels)es is a list of tuples ((v1,v2),el) where
v1 and v2 are vertices occurring in vs, representing a directed edge from v1 to v2el has EdgeLabelType and is the unique label associated with edge (v1,v2)(v1,v2) occurs at most once in vs (i.e., es encodes a function from edges to edge labels)In terms of the abstract model, vs encodes VL directly and, because VL is a total function on V, it encodes V indirectly. Similarly, es encodes EL directly and E indirectly.
Given the above description, we then define the following implementation (representation) invariant for the list-based version of the Labeled Digraph ADT:
Any Haskell
Digraphvalue(Graph vs es)with abstract modelG = (V,E,VL,EL), appearing in either the arguments or return value of an operation, must also satisfy the following:
(ForAll v, l :: (v,l) IN vs <=> (v,l) IN VL ) &&
(ForAll v1, v2, m :: (v1,v2,m) IN es <=> ((v1,v2),m) IN EL )
The Haskell module for the list representation of the labeled digraph graph ADT is in file DigraphADT_List.hs. Its test driver module is in file DigraphADT_TestList.hs.
The Haskell Map representation uses the following values for the type parameters:
VertexType is an instance of Haskell classes Ord and Show (i.e., can be compared and also converted to strings)
VertexLabelType is an instance of Haskell classes Eq and Show.
EdgeLabelType is an instance of Haskell classes Eq and Show.
Note: In the List version of this ADT, VertexType is required to be in classes Show and Eq (instead of Ord). The two label types did not require Eq. However, the use of the Map module for implementation in this version requires the new type constraints.
This implementation represents a labeled digraph as an instance of the Haskell algebraic data type Digraph, in particular data constructor (Graph m), where m is from the Data.Map.Strict collection. (This collection is implemented as a balanced tree.)
An instance of (Graph m) corresponds to the abstract model as follows:
The keys for Map m are from VertexLabelType.
Map m is defined for all keys v1 in vertex set V and undefined for all other keys.
For some vertex v1, the value of m at key v1 is a pair (l,es) where
l is an element of VertexLabelType and is the unique label associated with v1, that is, l = VL(v1).
es is the list of all tuples (v2,el) such that (v1,v2) IN E, el IN EdgeLabelType, and el = EL((v1,v2)). That is, (v1,v2) is an edge and el is its unique label.Given the above description, we then define the following implementation (representation) invariant for the list-based version of the Labeled Digraph ADT:
Any Haskell
Digraphvalue(Graph m)with abstract modelG = (V,E,VL,EL), appearing in either the arguments or return value of an operation, must also satisfy the following:
(ForAll v1, l, es ::
( m(v1) defined && m(v1) == (l,es) ) <=>
( VL(v1) == l &&
(ForAll v2, el :: (v2,el) IN es <=>
EL((v1,v2)) == el) ) )
The Haskell module for the Map representation of the labeled digraph graph ADT is in file DigraphADT_Map.hs. Its test driver module is in file DigraphADT_TestMap.hs.
In Spring 2017, I adapted and revised this document from comments in my Spring 2015 Haskell implementations of the Labeled Digraph abstract data type. (In addition to the list- and map-based Haskell implementations, I also developed a list-based implementation in Elixir in Spring 2015 and two Scala-based implementations in Spring 2016.)
In Summer and Fall 2017, I continue to revise this document. I plan eventually to integrate it into evolving textbook "Introduction to Functional Programming Using Haskell."
I maintain these notes as text in Pandoc's dialect of Markdown using embedded LaTeX markup for the mathematical formulas and then translate the notes to HTML, PDF, and other forms as needed. The HTML version of this document requires use of a browser that supports the display of MathML.
Use of Haskell module hiding features to implement the abstract data type's interface, applying specification concepts, using mathematical concepts to model the data abstraction (graphs, sets, sequences, bags, functions, relations), graph data structure.
The specification model for the abstract data type uses a constructive semantics -- an abstract model, invariants, preconditions, and postconditions.