Here's a final pipes example then. I don't think there's a way to fix the problem as Oleg proposed because pipes are monad transformers by design.

The Pipe monad transformer augments the base monad with two operations:
- await: gets a result from an upstream pipe
- yield: sends a result to a downstream pipe

I have a producer (which is a pipe that can only 'yield') that produces the lines of the .CSV file as Strings and returns () when done:

getFileContentsLifted :: Producer String IO ()
getFileContentsLifted = withCSVLifted "data.csv" myReadFile
    where
        myReadFile :: Handle -> Producer String IO ()
        myReadFile handle = do
                            eof <- lift $ hIsEOF handle
                            unless eof $ do
                                str <- lift $ hGetLine handle
                                yield str
                                myReadFile handle

I then have a simple pipeline that reads each line and prints it twice:

lineDoubler :: Pipe String String IO ()
lineDoubler = forever $ do
            s <- await
            yield s
            yield s

main = do
    runEffect $ getFileContentsLifted >-> lineDoubler >-> stdoutLn

The problem as before is that this code does not work with the original version of withCSV:

withCSV :: FilePath -> (Handle -> IO r) -> IO r
withCSV path action = do
    putStrLn "opening file"
    h <- openFile path ReadMode
    r <- action h
    hClose h
    putStrLn "file closed"
    return r

only with the lifted (i.e. generalized) one.

withCSVLifted :: MonadIO mIO => FilePath -> (Handle -> mIO r) -> mIO r
withCSVLifted path action = do
    liftIO $ putStrLn "opening file"
    h <- liftIO $ openFile path ReadMode
    r <- action h
    liftIO $ hClose h
    liftIO $ putStrLn "file closed"
    return r

And I have the same question: Should I always "generalize" my monadic actions that take callbacks as parameters?

I hope this version is still clear. Thanks for everyone for their input. I thought this was an easier problem than it now appears to be.

Dimitri

PS. Full code is here https://gist.github.com/dimitri-xyz/f1f5bd4c0f7f2bf85379


On 10/26/15 10:47 AM, Kim-Ee Yeoh wrote:

On Mon, Oct 26, 2015 at 11:36 PM, Dimitri DeFigueiredo <defigueiredo@ucdavis.edu> wrote:
I might have over simplified the problem by using ReaderT in my example. In my original problem this role is played by the Pipes library (and instead of using 'ask', I wanted to 'yield' control to a downstream pipe).

Is there a way you could introduce just enough complexity to allow Oleg another stab?

Also, there's always the fallback of showing your Pipes-based code although that library doesn't enjoy universal familiarity.

-- Kim-Ee