懒惰地评估Haskell中的monadic函数

时间:2011-05-09 16:38:05

标签: haskell monads lazy-evaluation

我似乎无法找到解决这个问题的方法。

我有这样的事情:

  getFilePathForDay :: Day -> IO (Maybe FilePath)

  getFilePathForDays date days = do
      files <- mapM getFilePathForDay $ iterate (addDays 1) date
      return . take days . catMaybes $ files

我正在尝试获取x个有效文件路径,其中x是我想要的天数,但上面的代码只是永远运行。我之前遇到过monad而遇到了这个问题,我想知道是否有一个解决方法可以解决我遇到的这个问题。

谢谢!

2 个答案:

答案 0 :(得分:8)

代码永远运行,因为您试图在调用mapM时对无限多个副作用计算进行排序。

由于您事先并不知道需要运行多少这些计算(如使用catMaybes所示),因此您必须将调用交错为getFilePathForDay剩下的计算。这可以使用unsafeInterleaveIO来完成,但顾名思义,这不是推荐的方法。

您可以手动实现:

getFilePathForDays _ 0 = return []
getFilePathForDays date days = do
    mpath <- getFilePathForDay date
    case mpath of
        Just path -> (path :) <$> remaining (days-1)
        Nothing   -> remaining days
  where
    remaining days = getFilePathForDays (addDays 1 date) days

可能有一种更优雅的方式,但现在还没有找到我:)

答案 1 :(得分:4)

有时懒惰的IO是正确的方法。由于你有一个无限的,懒惰的日子序列,并且你正在从该序列中懒散地采样一些(动态)范围,我们也应该只按需点击文件系统。

可以使用unsafeInterleaveIO

完成此操作
import System.IO.Unsafe         ( unsafeInterleaveIO )

-- return a lazy list of filepaths, pull as many as you need
getFilePathForDays :: Day -> Int -> IO [FilePath]
getFilePathForDays date days = do

    let go (d:ds) = unsafeInterleaveIO $ do
                        f  <- getFilePathForDay d
                        fs <- go ds
                        return (f:fs)

    -- an infinite, lazy stream of filepaths
    fs <- go (iterate (addDays 1) date)

    return . take days . catMaybes $ fs

go返回一个懒惰的结果流,我们会尽可能多地使用它们。容易。