[Haskell-cafe] Re: Re: Bulletproof resource management
Florian Weimer
fw at deneb.enyo.de
Sun Nov 28 13:47:40 CET 2010
* Ben Franksen:
>> The other library might provide something like IORef, and then
>> it's impossible to uphold static guarantees.
>
> The way it is implemented for instance in the regions package, you can lift
> IO actions into the Region monad, as there are
>
> instance MonadCatchIO pr => MonadCatchIO (RegionT s pr)
> instance MonadIO pr => MonadIO (RegionT s pr)
>
> Or maybe I don't understand your objection?
The other library would need to be lowered to the IO monad, which
seems to put undue constraints on resource lifetimes.
>> IMHO, this rules out such an approach (until it's part of the standard
>> library and forced upon everyone, which seems unlikely).
>
> I don't think it is necessary or even desirable to enforce this, or any
> other style of programming, especially not by standard library design.
So what are the other options, besides manually writing something
based on some IORef Maybe ForeignPtr a?
In my opinion, the handle-based style is more general than the
regions-based style. Resources in a cache lack a proper region-based
life-time, for instance. It is possible to go back from region-based
resource management to handles, but you need to spawn a thread for
each resource, just as in Erlang:
module Main where
import Prelude (putStrLn)
import Handles
main = do
h <- newHandle
openHandle h "/etc/passwd"
line1 <- getLine h
line2 <- getLine h
closeHandle h
putStrLn line1
putStrLn line2
module Handles (Handle, newHandle, openHandle, closeHandle, getLine) where
import Prelude (IO, String, error, ($), return, show)
import Data.Maybe
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar
import Control.Exception (IOException)
import System.Path (asAbsPath)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.CatchIO (catch)
import System.IO.SaferFileHandles hiding (getLine)
data Request = Open String
| Close
| GetLine
data Result = Success
| Failure String
| Line String
data Handle = MkHandle {
request :: MVar Request,
result :: MVar Result
}
newHandle :: IO Handle
newHandle = do
req <- newEmptyMVar
res <- newEmptyMVar
let h = MkHandle {request = req, result = res} in do
forkIO $ worker h
return h
forcePut :: MVar a -> a -> IO ()
forcePut mvar v = do
okay <- tryPutMVar mvar v
if okay then return ()
else error "invalid state"
-- Erlang-style worker process which encapsulates the state.
worker :: Handle -> IO ()
worker MkHandle {request = request, result = result} = runRegionT $ do
req <- liftIO $ takeMVar request
case req of
Open path -> do
-- FIXME: error handling
handle <-openFile (asAbsPath path) ReadMode
liftIO $ forcePut result Success
run handle
Close -> return ()
_ -> liftIO $ forcePut result $ Failure "handle not open"
where run handle = do
req <- liftIO $ takeMVar request
case req of
Open _ -> do
liftIO $ forcePut result $ Failure $ "handle already open"
Close -> return ()
GetLine -> do
s <- hGetLine handle
liftIO $ forcePut result $ Line s
run handle
openHandle :: Handle -> String -> IO ()
openHandle MkHandle {request = request, result = result} path = do
forcePut request (Open path)
res <- takeMVar result
case res of
Success -> return ()
Failure s -> error s
closeHandle :: Handle -> IO ()
closeHandle MkHandle {request = request} =
forcePut request Close
getLine :: Handle -> IO String
getLine MkHandle {request = request, result = result} = do
forcePut request GetLine
res <- takeMVar result
case res of
Line s -> return s
Failure s -> error s
More information about the Haskell-Cafe
mailing list