
Just to finish beating this horse: let me explain why the function with the following signature is actually a bad idea.
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
It accepts the callback that can do any MonadIO action, really any, including an action that throws an exception. If the callback 'action' throws an exception, who would close the CSV file? Further, the mIO action could do a non-deterministic choice. One may think of a such a choice as `forking' a lightweight `thread': m1 `mplus` m2 could be understood as forking a thread to execute m2, whereas the parent will execute m1. If the parent dies (if the m1 action or its consequences turned out unsuccessful), the child is awoken. The parent thread will eventually return through withCSVLifted and the CSV file will be closed. Suppose, the parent dies shortly after that. The child wakes up and tries to read from the Handle, which by that time will already be closed. (UNIX fork(2) does not have such problem because the OS duplicates not only the address space of the process but also all open file descriptors.) There is another problem with the withCSVLifted signature: the type of the return value is unrestricted. Therefore, r may be instantiated to Handle (or, more subtly, to the type of a closure containing a Handle), hence leaking the Handle out of the scope of withCSVLifted. The goal of withCSVLifted is to encapsulate a resource (file handle). Such encapsulation is far more difficult than it appears. I will recommend the old paper http://okmij.org/ftp/Haskell/regions.html#light-weight that illustrates these problems with simple approaches. It was a pleasant surprise that extensible effects (Haskell 2015 paper) turn out to implement monadic regions simpler than before. Please see Sec 7 of that paper (Freer monads, more extensible effects). The section also talks about the restrictions we have to impose on effects that are allowed to cross the region's boundary, so to speak.
pipes are monad transformers by design. That is regrettable.
BTW, the Haskell 2015 paper describes the extensible-effect implementation of Reader and Writer effects. Those effects are more general than their name implies: Reader is actually an iteratee and Writer can write to a file. As to Monad Control, that was recently mentioned: there is a problem with them that is explained in Sec 6 of the Haskell 2015 paper.