I'm trying to write a function that drops a list of tables from a database (e.g., tables A, B and C). This function has the following type, it returns a list of booleans indicating whether each table was successfully dropped:
dropTables :: (IConnection conn) => conn -> [String] -> IO [Bool]
Now, imagine table C depends on table B, i.e., table B cannot be dropped while table C exists. The function will be able to drop tables A and C, but not B, and so it will return
[True, False, True].
So, I wanted to write a function that takes a list of tables, and calls
dropTables. If any of the operations fail, it calls
dropTables again but this time only with the tables that could not be dropped in the first iteration. And so on, until either all tables have been dropped, or progression halts (i.e. all operations in a given iteration fail, due to e.g. a connection being closed)
This was my first stab at it:
dropTablesRec :: IConnection conn => conn -> [String] -> IO () dropTablesRec conn tables = do results <- dropTables conn tables let failures = map fst $ filter (not . snd) $ zip tables results unless (null failures) $ if length failures == length tables then print $ "Failed to drop the following tables: " ++ intercalate ", " failures else dropTablesRec conn failures
I then tried to generalize the core algorithm, and remove all references to tables and database connections:
-- Repeats a given action for a set of elements, until it succeeds for all elements, or fails for all elements. -- Elements for which the operation succeeds are not passed onto the next iteration. -- Returns the elements for which the action failed. repeatUntilAll :: Monad m => [a] -> ([a] -> m [Bool]) -> m [a] repeatUntilAll xs f = let go  = return  go failures | length failures == length xs = return failures -- halt | otherwise = repeatUntilAll failures f in do results <- f xs let failures = map fst $ filter (not . snd) $ zip xs results go failures
dropTablesRec can now be reduced to:
dropTablesRec' :: IConnection conn => conn -> [String] -> IO () dropTablesRec' conn tables = do failures <- repeatUntilAll tables (dropTables conn) unless (null failures) $ print $ "Failed to drop the following tables: " ++ intercalate ", " failures
I was wondering if there's a simpler way of implementing
I had a look around
Control.Monad.Loops to see if there was anything I could use, but no such luck. Being a Haskell novice doesn't help either, I'm only familiar with a very small part of the core libraries.
Other improvements (naming, conventions, greater generalization) are also welcome.