Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IO woes when seeking sizes of directory contents?

Tags:

haskell

I'm learning Haskell, and my goal today is to write a function sizeOf :: FilePath -> IO Integer (calculate the size of a file or folder), with the logic

  • If path is a file, System.Directory.getFileSize path
  • If path is a directory, get a list of its contents, recursively run this function on them, and sum the results
  • If it's something other than a file or directory, return 0

Here's how I'd implement it in Ruby, to illustrate (Ruby notes: map's argument is the equivalent of \d -> size_of d, reduce :+ is foldl (+) 0, any function ending ? returns a bool, returns are implicit):

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

Here's my crack at it in 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

I know where my problem is. sum $ map sizeOf $ listDirectory path, where listDirectory path returns an IO [FilePath] and not a FilePath. But... I can't really imagine any solution solving this. <$> instead of $ was the first thing that came to mind, since <$> I understood to be something that let a function of a -> b become a Context a -> Context b. But... I guess IO isn't like that?

I spent about two hours puzzling over the logic there. I tried it on other examples. Here's a relevant discovery that threw me: if double = (*) 2, then map double [1,2,3] == [2,4,6], but map double <$> [return 1, return 2, return 3] == [[2],[4],[6]]... it wraps them in a list. I think that's what happening to me but I'm way out of my depth.

like image 509
GreenTriangle Avatar asked Dec 24 '16 11:12

GreenTriangle


Video Answer


1 Answers

You'd need

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

Explanation:

  • The idea to use sum over an IO [Integer] is ok, so we need to get such a thing.
  • listDirectory path gives us IO [FilePath], so we need to pass each file path to sizeOf. This is what >>= together with mapM does.
  • Note that map alone would give us [IO Integer] that is why we need mapM
like image 74
Ingo Avatar answered Nov 15 '22 10:11

Ingo