module Database.Schema.Migrations.CycleDetection ( hasCycle ) where import Data.Graph.Inductive.Graph ( Graph(..) , Node , nodes , edges ) import Control.Monad.State ( State, evalState, gets, get, put ) import Control.Monad ( forM ) import Data.Maybe ( fromJust ) import Data.List ( findIndex ) data Mark = White | Gray | Black type CycleDetectionState = [(Node, Mark)] -- Cycle detection algorithm taken from http://www.cs.berkeley.edu/~kamil/teaching/sp03/041403.pdf hasCycle :: Graph g => g a b -> Bool hasCycle g = evalState (hasCycle' g) [(n, White) | n <- nodes g] getMark :: Int -> State CycleDetectionState Mark getMark n = gets (fromJust . lookup n) replace :: [a] -> Int -> a -> [a] replace elems index val | index > length elems = error "replacement index too large" | otherwise = (take index elems) ++ [val] ++ (reverse $ take ((length elems) - (index + 1)) $ reverse elems) setMark :: Int -> Mark -> State CycleDetectionState () setMark n mark = do st <- get let index = fromJust $ findIndex (\(n', _) -> n' == n) st put $ replace st index (n, mark) hasCycle' :: Graph g => g a b -> State CycleDetectionState Bool hasCycle' g = do result <- forM (nodes g) $ \n -> do m <- getMark n case m of White -> visit g n _ -> return False return $ or result visit :: Graph g => g a b -> Node -> State CycleDetectionState Bool visit g n = do setMark n Gray result <- forM [ v | (u,v) <- edges g, u == n ] $ \node -> do m <- getMark node case m of Gray -> return True White -> visit g node _ -> return False case or result of True -> return True False -> do setMark n Black return False