Search code examples
haskellhaskell-turtle

How to print paths using Haskell Turtle library?


To learn a bit about Turtle, I thought it would be nice to modify example from the tutorial. I chose to remove the reduntant "FilePath" from each line of the output thinking it would be a simple exercise.

And yet, despite author's efforts into making his library easy to use I nearly failed to use it to solve this simple problem.

I tried everyting I saw that looked like it would allow me to somehow lift >>= from IO into Shell: MonadIO, FoldM, liftIO, _foldIO with no success. I grew frustrated and only through reading Turtle source code I was able to find something that seems to work ("no obvious defects" comes to mind).

Why is this so hard? How does one logically arrive a solution using API of this library?

#!/usr/bin/env stack
-- stack --resolver lts-8.17 --install-ghc runghc --package turtle --package lens
{-# LANGUAGE OverloadedStrings #-}
import Turtle
import Control.Lens
import Control.Foldl as Foldl
import Filesystem.Path.CurrentOS
import Data.Text.IO as T
import Data.Text as T

main = do
  homedir <- home
  let paths = lstree $ homedir </> "projects"
  let t = fmap (Control.Lens.view _Right . toText) paths
  customView t

customView s = sh (do
  x <- s
  liftIO $ T.putStrLn x)

Solution

  • You don't lift >>= from IO into Shell. Shell already has a Monad instance that comes with its own >>= function. Instead you either lift IO actions into Shell with liftIO or run the shell with fold or foldM. Use sh to run the Shell when you don't care about the results.

    I believe your example can be simplified to

    main = sh $ do
      homedir <- home
      filepath <- lstree $ homedir </> "projects"
      case (toText filepath) of
        Right path -> liftIO $ T.putStrLn x
        Left approx -> return () -- This shouldn't happen
    

    As for the difficulty with getting a string back from a FilePath, I don't think that can be blamed on the Turtle author. I think it can be simplified to

    stringPath :: FilePath -> String
    stringPath filepath =
      case (toText filePath) of              -- try to use the human readable version
         Right path -> T.unpack path 
         Left _     -> encodeString filePath -- fall back on the machine readable one
    

    Combined this would simplify the example to

    main = sh $ do
      homedir <- home
      filepath <- lstree $ homedir </> "projects"
      liftIO $ putStrLn (stringPath filepath)
    

    or

    main = view $ do
      homedir <- home
      filepath <- lstree $ homedir </> "projects"
      return $ stringPath filepath