Search code examples
haskellhaskell-turtle

Haskell Turtle get out of Shell Monad


could you please help me with Turtle library. I want to write simple program, that calculates disk space usage. Here is the code:

getFileSize :: FilePath -> IO Size
getFileSize f = do
  status <- stat f
  return $ fileSize status

main = sh $ do
  let sizes = fmap getFileSize $ find (suffix ".hs") "."

so now I have sizes bind of type Shell (IO Size). But I can't just sum it, with sum fold, cause there is IO Size in there. If it was something like [IO Size] I could pull IO monad out of there by using sequence to transform it to IO [Size]. But I can't do this with Shell monad since it is not Traversable. So I wrote something like this

import qualified Control.Foldl as F

main = sh $ do
  let sizes = fmap getFileSize $ find (suffix ".hs") "."
  lst <- fold sizes F.list
  let cont = sequence lst
  sz <- liftIO $ cont
  liftIO $ putStrLn (show (sum sz))  

First I folded Shell (IO Size) to [IO Size] and then to IO [Size] to sum list afterwards. But I wonder if there is more canonical or elegant solution to this, because here I created two lists to accomplish my task. And I throught that Shell monad is for manipulating entities in constant space. Maybe there is some fold to make IO (Shell Size) from Shell (IO Size)?

Thanks.


Solution

  • You have an IO action, and you really want a Shell action. The usual way to handle that is with the liftIO method, which is available because Shell is an instance of MonadIO.

    file <- find (suffix ".hs") "."
    size <- liftIO $ getFileSize file
    

    or even

    size <- liftIO . getFileSize =<< find (suffix ".hs") "."
    

    Fortunately, the Turtle package itself offers some size functions you can use directly with MonadIO instances like Shell in Turtle.Prelude so you don't need to use liftIO yourself.

    Now you actually have to sum these up, but you can do that with fold and sum.

    I would recommend that you avoid breaking open the Shell type itself. That should be reserved for adding totally new functionality to the API. That certainly isn't necessary in this case.