在寻找目录内容大小时,IO会遇到什么困难?

时间:2016-12-24 11:41:04

标签: haskell

我正在学习Haskell,我今天的目标是编写一个函数sizeOf :: FilePath -> IO Integer(计算文件或文件夹的大小),逻辑

  • 如果path是文件,System.Directory.getFileSize path
  • 如果path是一个目录,请获取其内容列表,以递归方式运行此函数,并sum结果
  • 如果它不是文件或目录,return 0

以下是我在Ruby中实现它的方法,以说明(Ruby注意:map的参数相当于\d -> size_of dreduce :+foldl (+) 0,任何函数结束?返回bool,返回是隐式的):

def size_of path
  if File.file? path
    File.size path
  elsif File.directory? path
    Dir.glob(path + '/*').map { |d| size_of d }.reduce :+
  end
end

这是我在Haskell中对它的抨击:

  sizeOf :: FilePath -> IO Integer
  sizeOf path =
    do
      isFile <- doesFileExist path
      if isFile then
        getFileSize path
      else do
        isDir <- doesDirectoryExist path
        if isDir then
          sum $ map sizeOf $ listDirectory path
        else
          return 0

我知道我的问题在哪里。 sum $ map sizeOf $ listDirectory path,其中listDirectory path返回IO [FilePath]而不是FilePath。但是......我无法想象任何解决这个问题的解决方案。首先想到的是<$>而不是$,因为<$>我理解为让a -> b的函数成为Context a -> Context b。但是......我猜IO不是那样的?

我花了大约两个小时对那里的逻辑感到困惑。我在其他例子上尝试过。这是一个相关的发现,它让我感动:如果double = (*) 2,那么map double [1,2,3] == [2,4,6],但是map double <$> [return 1, return 2, return 3] == [[2],[4],[6]] ......它将它们包含在一个列表中。我想这就是发生在我身上的事情,但我已经走出了我的深度。

2 个答案:

答案 0 :(得分:5)

你需要

sum <$> (listDirectory path >>= mapM sizeOf)

说明:

  • 使用sum而不是IO [Integer]的想法是可以的,所以我们需要得到这样的东西。
  • listDirectory path向我们提供了IO [FilePath],因此我们需要将每个文件路径传递给sizeOf。这是>>=mapM一起做的事情。
  • 请注意map单独会给我们[IO Integer]这就是我们需要mapM
  • 的原因

答案 1 :(得分:1)

如何(使用Control.Monad.Extra):

du :: FilePath -> IO Integer
du path = ifM (doesFileExist path)
              (getFileSize path) $
              ifM (doesDirectoryExist path)
                  (sum <$> (listDirectory path >>= mapM (du . 
                             (addTrailingPathSeparator path ++))))
                  (return 0)

我认为您需要添加listDirectory输出的路径才能成功递归下降,因为listDirectory只返回没有路径的文件名,这是后续调用du所必需的。

ifM的类型可能很明显,但是ifM :: Monad m => m Bool -> m a -> m a -> m a