Hi Chris,

You are right. The implementation is totally dodgy! In fact, Pipes already hasĀ  fromHandle which does this properly. I'm just trying to come up with an example of an IO action that takes another IO action as a parameter and what to do about using that with a monad transformer such as pipes. My focus is what to do when you need to use an action such as withCSV with a action that is *not* in the IO monad.

Dimitri

On 10/27/15 5:52 PM, Chris Wong wrote:
Hi Dimitri,

The implementation of `withCSVLifted` looks dodgy to me. If a
downstream consumer terminates early, then the file will never get
closed.

For pipes, the standard solution to resource management is the
pipes-safe[1] package. It handles early termination and IO exceptions
automatically. The example in the docs should fit your use case pretty
well.

[1] https://hackage.haskell.org/package/pipes-safe-2.2.3/docs/Pipes-Safe.html

On Wed, Oct 28, 2015 at 12:25 PM, Dimitri DeFigueiredo
<defigueiredo@ucdavis.edu> wrote:
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



_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe