3

Is it possible to split a Shell in Turtle library (Haskell) and do different things to either split of the shell, such that the original Shell is only run once ?

             /---- shell2
---Shell1 --/
            \
             \-----shell3

For instance, how to do

do
  let lstmp = lstree "/tmp"
  view lstmp
  view $ do
    path <- lstmp
    x <- liftIO $ testdir path
    return x

such that lstree "/tmp" would only run once.

Specifically I would like to send Shell 2 and Shell 3 to different files using output.

miguel.negrao
  • 949
  • 5
  • 13

2 Answers2

3

You won't be able to split a Shell into two separate shells that run simultaneously, unless there's some magic I don't know. But file writing is a fold over the contents of a shell or some other succession of things. It is built into turtle that you can always combine many folds and make them run simultaneously using the Control.Foldl material - here

foldIO ::  Shell a -> FoldM IO a r -> IO r  -- specializing

A shell is secretly a FoldM IO a r -> IO r under the hood anyway, so this is basically runShell. To do this we need to get the right Shell and the right combined FoldM IO. The whole idea of the Fold a b and FoldM m a b types from the foldl package is simultaneous folding.

I think the easiest way to get the right shell is just to make the lstree fold return a FilePath together with the result of testdir. You basically wrote this:

withDirInfo :: FilePath -> Shell (Bool, FilePath)
withDirInfo tmp = do
    let lstmp = lstree tmp
    path <- lstmp
    bool <- liftIO $ testdir path
    return (bool, path)

So now we can get a Shell (Bool, FilePath) from /tmp This has all the information our two folds will need, and thus that our combined fold will need.

Next we might write a helper fold that prints the Text component of the FilePath to a given handle:

sinkFilePaths :: Handle -> FoldM IO FilePath ()
sinkFilePaths handle = L.sink (T.hPutStrLn handle . format fp)

Then we can use this Handle -> FoldM IO FilePath () to define two FoldM IO (Bool, FilePath) (). Each will write different stuff to different handles, and we can unite them into a single simultaneous fold with <*. This is an independent FoldM IO ... and can be applied e.g. to a pure list of type [(Bool, FilePath)] using L.fold and it will write different things from the list to the different handles. In our case, though, we will apply it to the Shell (Bool, FilePath) we defined.

The only subtle part of this is the use of L.handlesM to print only the second element, in both cases, and only those filtered as directories in the other. This uses the _2 lens and filtered from the lens libraries. This could probably be simplified, but see what you think:

{-#LANGUAGE OverloadedStrings #-}
import Turtle
import qualified Control.Foldl as L
import qualified System.IO as IO
import Control.Lens (_2,filtered)
import qualified Data.Text.IO as T

main = IO.withFile "tmpfiles.txt" IO.WriteMode $ \h ->
       IO.withFile "tmpdirs.txt" IO.WriteMode $ \h' -> do
        foldIO (withDirInfo "/tmp") (sinkFilesDirs h h') 

withDirInfo :: Turtle.FilePath -> Shell (Bool, Turtle.FilePath)
withDirInfo tmp = do
    let lstmp = lstree tmp
    path <- lstmp
    bool <- liftIO $ testdir path
    return (bool, path)

sinkFilePaths :: Handle -> FoldM IO Turtle.FilePath ()
sinkFilePaths handle = L.sink (T.hPutStrLn handle . format fp)

sinkFilesDirs  :: Handle -> Handle -> FoldM IO (Bool, Turtle.FilePath) ()
sinkFilesDirs h h' = allfiles <* alldirs where

  allfiles :: L.FoldM IO (Bool, Turtle.FilePath) ()
  allfiles = L.handlesM _2 (sinkFilePaths h)
  -- handle the second element of pairs with sinkFilePaths

  alldirs :: FoldM IO (Bool, Turtle.FilePath) ()
  alldirs = L.handlesM (filtered (\(bool,file) -> bool) . _2) (sinkFilePaths h')
 -- handle the second element of pairs where the first element
 -- is true using sinkFilePaths
Michael
  • 2,889
  • 17
  • 16
  • Ok, that explains very nicely how to do it. The part that I was missing was understanding that writing to a file can also be a fold using 'L.sink'. Just means I have to explore more the power of Foldl ! – miguel.negrao May 19 '16 at 15:02
0

It sounds like you're looking for something like async to split off your shells from the first shell and then wait for them to return. async is a pretty capable library that can achieve much more than the below example, but it provides a pretty simple solution to what you're asking for:

import Control.Concurrent.Async
import Turtle.Shell
import Turtle.Prelude

main :: IO ()
main = do
  let lstmp1 = lstree "/tmp"
  let lstmp2 = lstree "/etc"
  view lstmp1
  view lstmp2
  job1 <- async $ view $ do
    path <- lstmp1
    x <- liftIO $ testdir path
    return x
  job2 <- async $ view $ do
    path <- lstmp2
    x <- liftIO $ testdir path
    return x
  wait job1
  wait job2

Is this what you're looking for?

carpemb
  • 691
  • 1
  • 8
  • 19
  • I'm doing stream processing, so actually I want everything to happen in sync, line by line, so I don't think async is the answer here. – miguel.negrao May 19 '16 at 14:59