哈斯克尔。从纯代码到IO和返回

时间:2013-10-22 18:59:43

标签: haskell recursion io

当它抛出我们提供的一些异常时,是否有可能停止递归算法,保存它的状态,询问用户的内容然后从保存的地方继续递归?

我改变了问题。

我以递归方式读取文件系统并将数据保存在树中。突然,我面对一个隐藏的目录。我可以停止计算并询问用户是否应该在树中放置有关目录的信息然后继续计算?

关于使用IO:

obtainTree :: ByteString -> Tree
...
main = print $ obtainTree partition

据我所知,在算法中使用IO我们必须使用这样的函数:

obtainTree :: ByteString -> IO Tree

但我们可以避免吗?

2 个答案:

答案 0 :(得分:6)

当然可以做到。您可以随时进行设置,以便将剩余的计算作为延续捕获,可以从外部恢复。

这是做这样事情的一种方法:

-- 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

这使您可以将逻辑划分为在Resumable内部运行的内部部分以及使用其喜欢的任何方法处理内部部分提示结果的外部部分。

以下是使用此功能的简单示例:

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

作为一种简化此用例的方法,可以扩展包含Resumable的模块以导出此函数:

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

允许从该示例重写main,因为它更简单:

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

这种方法的一个真正问题是每个提示必须具有相同的类型。否则,它会很好地处理问题,使用continuation在需要时隐式捕获其余的计算。

答案 1 :(得分:0)

首先,纯代码不能转到IO,或者我们可以说如果它试图使用一些不纯的函数(即尝试使用IO),纯函数需要变得不纯。如果你想知道为什么会这样,想一想:如果纯函数询问一些数据的不纯函数来完成它自己的处理那么它就会失去“参照透明度”,因为现在纯函数可以返回相同输入的不同结果所涉及的不纯(IO)调用,因此它不再纯粹。

根据以上信息,您的解决方案将像使用更高阶函数一样简单地向用户询问信息。类似的东西:

parseFileSystem :: FileSystem -> (Directory -> IO Tree) -> IO Tree

此处(Directory -> IO Tree)是向用户询问所需信息并根据其返回Tree数据的功能。