Reputation: 1157
Are there a possibility to stop a recursive algorithm when it throws some exception provided by us, save it's state, ask user something and then continue the recursion from the saved place?
I changed the question.
I read a file system recursively and keep data in a tree. Suddenly I face with a hidden directory. Can I stop calculations and ask now user should I place information about the directory in my tree and then continue calculations?
About working with IO:
obtainTree :: ByteString -> Tree
...
main = print $ obtainTree partition
as I understand to work with IO inside the algorithm we have to use function like this:
obtainTree :: ByteString -> IO Tree
but can we avoid it?
Upvotes: 0
Views: 294
Reputation: 33637
First thing first, a pure code cannot go to IO or we can say a pure function needs to become impure if it tries to use some impure function (i.e trying to use IO). In case you are wondering why this so, think about this: If the pure function ask the impure function about some data to complete its own processing then it looses "Referential transparency" because now the pure function can return different result for same input due to the involved impure (IO) call, hence it is no more pure.
Based on the above information, your solution will be as simple as making use of higher order function to ask the user about the information. Something like:
parseFileSystem :: FileSystem -> (Directory -> IO Tree) -> IO Tree
Here the (Directory -> IO Tree)
is the function that will ask user about the required information and return a Tree
data based on it.
Upvotes: 0
Reputation: 27003
Sure you can do it. You can always set things up so that you capture the remaining computation as a continuation, which can be resumed externally.
Here's one way to do something like this:
-- intended to be put in a module that only exports the following list:
-- (Resumable, Prompted, prompt, runResumable, extract, resume)
import Control.Applicative
newtype Resumable e r a = R { runResumable :: Either (Prompted e r a) a }
data Prompted e r a = P e (r -> Resumable e r a)
suspend :: e -> (r -> Resumable e r a) -> Resumable e r a
suspend e = R . Left . P e
instance Functor (Resumable e r) where
fmap f (R (Right x)) = pure $ f x
fmap f (R (Left (P e g))) = suspend e $ \x -> f <$> g x
instance Applicative (Resumable e r) where
pure = R . Right
(R (Right f)) <*> (R (Right x)) = pure $ f x
(R (Left (P e f))) <*> x = suspend e $ \y -> f y <*> x
f <*> (R (Left (P e g))) = suspend e $ \y -> f <*> g y
instance Monad (Resumable e r) where
return = pure
(R (Right x)) >>= f = f x
(R (Left (P e f))) >>= g = suspend e $ \x -> f x >>= g
prompt :: e -> Resumable e r r
prompt e = suspend e pure
extract :: Prompted e r a -> e
extract (P e _) = e
resume :: Prompted e r a -> r -> Either (Prompted e r a) a
resume (P _ f) e = runResumable $ f e
This lets you divide up your logic into an internal piece that runs inside Resumable
and an external piece that handles the results of the internal part's prompting using whatever method it likes.
Here's a simple example of using this:
askAboutNegatives :: [Int] -> Resumable Int Bool [Int]
askAboutNegatives [] = return []
askAboutNegatives (x:xs) = do
keep <- if x < 0 then prompt x else return True
rest <- askAboutNegatives xs
return $ if keep then x:rest else rest
main :: IO ()
main = do
let ls = [1, -4, 2, -7, 3]
loopIfNeeded (Right r) = return r
loopIfNeeded (Left p) = do
putStrLn $ "Would you like to keep " ++ show (extract p)
i <- getLine
loopIfNeeded $ resume p (i == "y")
asked <- loopIfNeeded $ runResumable (askAboutNegatives ls)
print asked
As a way of making this use case simpler, the module containing Resumable
can be augmented to also export this function:
runResumableWithM :: Monad m => (e -> m r) -> Resumable e r a -> m a
runResumableWithM f x = case runResumable x of
Right y -> return y
Left (P e g) -> do
r <- f e
runResumableWithM f $ g r
Which would allow rewriting main
from that example as the somewhat simpler:
main :: IO ()
main = do
let ls = [1, -4, 2, -7, 3]
ask x = do
putStrLn $ "Would you like to keep " ++ show x
i <- getLine
return $ i == "y"
asked <- runResumableWithM ask (askAboutNegatives ls)
print asked
The one real issue with this approach is that every prompt must have the same type. Otherwise, it handles the problem nicely, using continuations to capture the rest of the computation implicitly when needed.
Upvotes: 6