I am implementing a simple recursive Depth-First Search algorithm on graphs, using StateT and ReaderT for encapsulating all the required information.
Pre-requisite definitions
type Vertex = Int
type Cost = Int
data Edge = Edge { vertex:: Vertex, cost:: Cost } deriving (Eq, Show, Ord)
newtype Graph = Graph { adjacencyList :: Map.Map Vertex [Edge] } deriving (Eq, Show)
type DFSState = ([Vertex], [Vertex])
type Env = Graph
type DFSNextState = ReaderT Env (StateT DFSState Identity) Vertex
and then the main function:
{-# LANGUAGE FlexibleContexts #-}
dfsGetNext :: DFSNextState
dfsGetNext = do
graph <- ask
s <- get
let candidates = fst s
let visited = snd s
let ret = case candidates of
[] -> return 0 :: DFSNextState
(x:xs) -> if elem x visited
then put (xs, visited) >> (return 1 :: DFSNextState)
else put ((getNeighbors x graph) ++ xs, visited ++ [x]) >> (return 1 :: DFSNextState)
ret
I can then simply run the algorithm for n steps and get the nodes visited in a depth-first fashion using
dfsStartState :: DFSState
dfsStartState = ([0], [])
dfsSteps = replicateM 13 dfsGetNext
runStateT (runReaderT dfsSteps graph) dfsStartState
Questions:
1) Is my style in the main function good? Can it be simplified?
2) Is there a way to remove the magic number (13 in this case) for replicateM? Is there a replicate until which I could use for this?
Update I added a replicateMUntil function myself:
{-# LANGUAGE RankNTypes, KindSignatures #-}
replicateMUntil :: forall (m :: * -> *) a. Monad m => (m a -> m Bool) -> m a -> m [a]
replicateMUntil f ma = do
res <- f ma
if res then liftM2 (:) ma (replicateMUntil f ma) else return []
The driver now becomes:
isNextAvailable :: DFSNextState -> ReaderT Env (StateT DFSState Identity) Bool
isNextAvailable nextState = do
s <- get
return (fst s /= [])
dfsStartState :: DFSState
dfsStartState = ([0], [])
dfsSteps = replicateMUntil isNextAvailable dfsGetNext
runStateT (runReaderT dfsSteps graph) dfsStartState