我正在学习Haskell,我今天的目标是编写一个函数sizeOf :: FilePath -> IO Integer
(计算文件或文件夹的大小),逻辑
path
是文件,System.Directory.getFileSize path
path
是一个目录,请获取其内容列表,以递归方式运行此函数,并sum
结果return 0
以下是我在Ruby中实现它的方法,以说明(Ruby注意:map
的参数相当于\d -> size_of d
,reduce :+
是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]]
......它将它们包含在一个列表中。我想这就是发生在我身上的事情,但我已经走出了我的深度。
答案 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