packages feed

liboleg 0.1.0.2 → 0.1.0.3

raw patch · 3 files changed

+337/−1 lines, 3 filesPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

API changes (from Hackage documentation)

+ System.IterateeM: (==<<) :: (Monad m) => (IterateeG el m a -> IterateeGM el' m b) -> IterateeGM el m a -> IterateeGM el' m b
+ System.SafeHandles: AppendMode :: IOMode
+ System.SafeHandles: ReadMode :: IOMode
+ System.SafeHandles: ReadWriteMode :: IOMode
+ System.SafeHandles: WriteMode :: IOMode
+ System.SafeHandles: data IOMode :: *
+ System.SafeHandles: data IORT s m v
+ System.SafeHandles: data SHandle m :: (* -> *)
+ System.SafeHandles: instance [overlap ok] (Monad m) => Monad (IORT s m)
+ System.SafeHandles: instance [overlap ok] (Monad m) => MonadRaise m m
+ System.SafeHandles: instance [overlap ok] (Monad m2, TypeCast2 m2 (IORT s m2'), MonadRaise m1 m2') => MonadRaise m1 m2
+ System.SafeHandles: instance [overlap ok] (RMonadIO m) => RMonadIO (IORT s m)
+ System.SafeHandles: instance [overlap ok] (RMonadIO m) => RMonadIO (ReaderT r m)
+ System.SafeHandles: instance [overlap ok] (TypeCast' () a b) => TypeCast a b
+ System.SafeHandles: instance [overlap ok] (TypeCast'' t a b) => TypeCast' t a b
+ System.SafeHandles: instance [overlap ok] (TypeCast2' () a b) => TypeCast2 a b
+ System.SafeHandles: instance [overlap ok] (TypeCast2'' t a b) => TypeCast2' t a b
+ System.SafeHandles: instance [overlap ok] RMonadIO IO
+ System.SafeHandles: instance [overlap ok] TypeCast'' () a a
+ System.SafeHandles: instance [overlap ok] TypeCast2'' () a a
+ System.SafeHandles: liftSIO :: (Monad m) => IORT s m a -> IORT s1 (IORT s m) a
+ System.SafeHandles: newRgn :: (RMonadIO m) => (forall s. IORT s m v) -> m v
+ System.SafeHandles: newSHandle :: (RMonadIO m) => FilePath -> IOMode -> IORT s m (SHandle (IORT s m))
+ System.SafeHandles: runSIO :: (forall s. SIO s v) -> IO v
+ System.SafeHandles: sNewIORef :: (RMonadIO m) => a -> m (IORef a)
+ System.SafeHandles: sReadIORef :: (RMonadIO m) => IORef a -> m a
+ System.SafeHandles: sWriteIORef :: (RMonadIO m) => IORef a -> a -> m ()
+ System.SafeHandles: shCatch :: (RMonadIO m) => m a -> (Exception -> m a) -> m a
+ System.SafeHandles: shDup :: (RMonadIO m) => SHandle (IORT s1 (IORT s m)) -> IORT s1 (IORT s m) (SHandle (IORT s m))
+ System.SafeHandles: shGetLine :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> m2 String
+ System.SafeHandles: shIsEOF :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> m2 Bool
+ System.SafeHandles: shPutStrLn :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> String -> m2 ()
+ System.SafeHandles: shReport :: (RMonadIO m) => String -> m ()
+ System.SafeHandles: shThrow :: (RMonadIO m) => Exception -> m a
+ System.SafeHandles: type SIO s = IORT s IO

Files

System/IterateeM.hs view
@@ -85,6 +85,10 @@  -- | Just like an application -- a call-by-value-like application infixr 1 ==<<+(==<<) :: Monad m =>+          (IterateeG el m a -> IterateeGM el' m b) ->+          IterateeGM el m a ->+          IterateeGM el' m b f ==<< m = m >>== f  -- | The following is a `variant' of join in the IterateeGM el m monad.
+ System/SafeHandles.hs view
@@ -0,0 +1,331 @@+{-# LANGUAGE Rank2Types, KindSignatures, GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}++{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE UndecidableInstances, OverlappingInstances #-}++-- |+-- <http://okmij.org/ftp/Haskell/regions.html#light-weight>+-- +-- Lightweight monadic regions+-- +-- [The Abstract of the paper]+-- We present Haskell libraries that statically ensure the safe use of resources such as file+-- handles. We statically prevent accessing an already closed handle or forgetting to close it. The+-- libraries can be trivially extended to other resources such as database connections and graphic+-- contexts.+-- +-- Because file handles and similar resources are scarce, we want to not just assure their safe use+-- but further deallocate them soon after they are no longer needed. Relying on Fluet and+-- Morrisett's calculus of nested regions, we contribute a novel, improved, and extended+-- implementation of the calculus in Haskell, with file handles as resources.+-- +-- Our library supports region polymorphism and implicit region subtyping, along with higher-order+-- functions, mutable state, recursion, and run-time exceptions. A program may allocate arbitrarily+-- many resources and dispose of them in any order, not necessarily LIFO. Region annotations are+-- part of an expression's inferred type. Our new Haskell encoding of monadic regions as monad+-- transformers needs no witness terms. It assures timely deallocation even when resources have+-- markedly different lifetimes and the identity of the longest-living resource is determined only+-- dynamically.+-- +-- For contrast, we also implement a Haskell library for manual resource management, where+-- deallocation is explicit and safety is assured by a form of linear types. We implement the linear+-- typing in Haskell with the help of phantom types and a parameterized monad to statically track+-- the type-state of resources.+-- +-- Joint work with Chung-chieh Shan.+-- +--+-- Handle-based IO with the assured open/close protocol, see README+-- This file contains the Security kernel. See SafeHandlesTest.hs for tests.+-- This is the final solution: lightweight monadic regions with+-- only type-level enforcement of region discipline++module System.SafeHandles +    (IORT,                      -- constructors not exported+     SIO,+     SHandle,++     runSIO,+     newRgn,+     liftSIO,+     newSHandle,+     shDup,+     IOMode(..),                -- re-exported from System.IO++     shGetLine,+     shPutStrLn,+     shIsEOF,++     shThrow,+     shCatch,+     shReport,++     sNewIORef,                 -- IORef in SIO+     sReadIORef,+     sWriteIORef+     ) where++import System.IO+import Control.Exception+import Control.Monad.Reader+import Control.Monad.Trans+import Data.IORef+import Data.List (find)+import Prelude hiding (catch)+import GHC.IOBase (ioe_handle)+++-- | The IO monad with safe handles and regions (SIO) is implemented as +-- the monad transformer IORT (recursively) applied to IO.+--+-- Each region maintains the state listing all open +-- handles assigned to the region.+-- Since we already have IO, it is easy to implement the state as a +-- mutable list (IORef of the list) and make this reference+-- a pervasive environment. +-- We could have used implicit parameters or implicit configurations to+-- pass that IORef around. Here, we use ReaderT.++-- Since we do IO with our handles, we may be tempted to make+-- the IORT transformer an instance of MonadIO. However, that would +-- give the user the ability to do any IO and so defeat the safety+-- guarantees. The purpose of IORT is to restrict permissible IO+-- operations to an assured set. Since we do need something like MonadIO,+-- we define our own private version here, RMonadIO. It is not exported.+-- Unlike MonadIO, our RMonadIO also supports catching and +-- handling of exceptions.+--+-- A region is identified by a label, a type eigenvariable (see 's'+-- in ST s).+--+newtype IORT s m v = IORT{ unIORT:: ReaderT (IORef [HandleR]) m v } +    deriving (Monad)++type SIO s = IORT s IO++-- | A simple abstract data type to track the duplication of handles+-- using reference counting+data HandleR = HandleR Handle (IORef Integer)++close_hr :: HandleR -> IO ()+close_hr (HandleR h refcount) = do+    hPutStrLn stderr $ "Closing " ++ show h+    rc <- readIORef refcount+    assert (rc > 0) (return ())+    writeIORef refcount (pred rc)+    if rc > 1+       then hPutStrLn stderr "   Aliased handle wasn't closed"+       else hClose h++new_hr :: Handle -> IO HandleR+new_hr h = newIORef 1 >>= return . HandleR h++eq_hr :: Handle -> HandleR -> Bool+eq_hr h1 (HandleR h2 _) = h1 == h2++++-- | Lift from one IORT to an IORT in a children region...+-- IORT should be opaque to the user: hence this is not the instance +-- of MonadTrans+liftSIO :: Monad m => IORT s m a -> IORT s1 (IORT s m) a+liftSIO = IORT . lift++-- | Raise from one IORT to another: this class lets the user access+-- handles of an ancestor region+-- MonadRaise m1 m2 holds when either +--  * m2 is the same as m1, or+--  * m2 is the sequence of one or more IORT applied to m1.+-- In other words, MonadRaise m1 m2 holds if m1 is an improper+-- `suffix' of m2.+--+class (Monad m1, Monad m2) => MonadRaise m1 m2++-- | The following is the general definition. It required the following extensions+--+-- > {-# LANGUAGE EmptyDataDecls, FunctionalDependencies #-}+-- > {-# LANGUAGE UndecidableInstances, OverlappingInstances #-}+--+-- to implement the disjunction present in the definition of MonadRaise.+-- In particular, OverlappingInstances are needed for TEQ.++{-+ -- more complicated implementation+instance (TEQ m1 m2 eq, MonadRaise' m1 m2 eq) => MonadRaise m1 m2++class (Monad m1, Monad m2) => MonadRaise' m1 m2 eq+instance (Monad m1, Monad m2) => MonadRaise' m1 m2 HTrue+instance MonadRaise m1 m2 => MonadRaise' m1 (IORT s m2) HFalse++data HTrue+data HFalse++class TEQ (a :: * -> *) (b :: * -> *) eq | a b -> eq+instance TEQ a a HTrue+instance TypeCast eq HFalse => TEQ a b eq++-}++-- {-+-- A simpler implementation+instance Monad m => MonadRaise m m+instance (Monad m2, TypeCast2 m2 (IORT s m2'), MonadRaise m1 m2') +    => MonadRaise m1 m2+-- -}+++-- In GHC 6.6 and earlier, we could only write the following specialized+-- definition. It handles only the fixed number of levels.+{-+instance Monad m => MonadRaise m m+instance Monad m => MonadRaise m (IORT s1 m)+instance Monad m => MonadRaise m (IORT s2 (IORT s1 m))+instance Monad m => MonadRaise m (IORT s3 (IORT s2 (IORT s1 m)))+-}++-- | RMonadIO is an internal class, a version of MonadIO+class Monad m => RMonadIO m where+    brace :: m a -> (a -> m b) -> (a -> m c) -> m c+    snag  :: m a -> (Exception -> m a) -> m a+    lIO   :: IO a -> m a++instance RMonadIO IO where+    brace = bracket'+    snag  = catch'+    lIO   = id++-- | The following makes sure that a low-level handle (System.IO.Handle)+-- cannot escape in an IO exception. Whenever an IO exception is caught,+-- we remove the handle from the exception before passing it to the+-- exception handler.+catch':: IO a -> (Exception -> IO a) -> IO a+catch' m f = catch m (f . sanitizeExc)++bracket' :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c+bracket' before after m = +    bracket before after m `catch` (throwIO . sanitizeExc)++sanitizeExc :: Exception -> Exception+sanitizeExc e = +    maybe e (\e -> IOException e{ioe_handle = Nothing}) $ ioErrors e++instance RMonadIO m => RMonadIO (ReaderT r m) where+    brace before after during = ReaderT (\r ->+        let rr m = runReaderT m r+        in brace (rr before) (rr.after) (rr.during))+    snag m f = ReaderT(\r -> +                 runReaderT m r `snag` \e -> runReaderT (f e) r)+    lIO = lift . lIO++instance RMonadIO m => RMonadIO (IORT s m) where+    brace before after during = IORT+        (brace (unIORT before) (unIORT.after) (unIORT.during))+    snag m f = IORT ( unIORT m `snag` (unIORT . f) )+    lIO = IORT . lIO++-- | There is no explicit close operation. A handle is automatically+-- closed when its region is finished (normally or abnormally).+--+newRgn :: RMonadIO m => (forall s. IORT s m v) -> m v+newRgn m = brace (lIO (newIORef [])) after (runReaderT (unIORT m))+    where after handles = lIO (readIORef handles >>= mapM_ close)+          close h = catch (close_hr h) (\e -> return ())++runSIO :: (forall s. SIO s v) -> IO v+runSIO = newRgn++-- | Our (safe) handle is labeled with the monad where it was created+newtype SHandle (m :: * -> *) = SHandle Handle  -- data ctor not exported++-- | Create a new handle and assign it to the current region +-- One can use liftIORT (newSHandle ...) to assign the handle to any parent+-- region.+newSHandle :: RMonadIO m => FilePath -> IOMode -> IORT s m (SHandle (IORT s m))+newSHandle fname fmode = IORT r'+ where r' = do+            h <- lIO $ openFile fname fmode -- may raise exc+            handles <- ask+            hr <- lIO $ new_hr h+            lIO $ modifyIORef handles (hr:)+            return (SHandle h)+++-- | Safe-handle-based IO...+-- The handle is assigned to the current region or its ancestor.+-- So, we have to verify that the label of the handle is the prefix+-- (perhaps improper) of the label of the monad (label of the region).++shGetLine :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> m2 String+shGetLine (SHandle h) = lIO (hGetLine h)++shPutStrLn :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> String -> m2 ()+shPutStrLn (SHandle h) = lIO . hPutStrLn h++shIsEOF :: (MonadRaise m1 m2, RMonadIO m2) => SHandle m1 -> m2 Bool+shIsEOF (SHandle h) = lIO (hIsEOF h)++-- | Duplicate a handle, returning a handle that can be used in the parent+-- region (and can be returned from the current region as the result).+-- This operation prolongs the life of a handle based on a+-- _dynamic_ condition. If we know the lifetime of a handle statically,+-- we can execute liftSIO (newSHandle ...) to place the handle in the+-- corresponding region. If we don't know the lifetime of a handle+-- statically, we place it in the inner region, and then extend its lifetime+-- by reassigning to the parent region based on the dynamic conditions.+shDup :: RMonadIO m => +         SHandle (IORT s1 (IORT s m)) -> IORT s1 (IORT s m) (SHandle (IORT s m))+shDup (SHandle h) = IORT (do+   handles <- ask >>= lIO . readIORef+   let Just hr@(HandleR _ refcount) = find (eq_hr h) handles+   lIO $ modifyIORef refcount succ+   lift $ IORT (do              -- in the parent monad+                  handles <- ask+                  lIO $ modifyIORef handles (hr:))+   return (SHandle h))++-- | It seems however that IOErrors don't invalidate the Handles.+-- For example, if EOF is reported, we may try to reposition the `file'+-- and read again. That's why in Posix, EOF and file errors can be cleared.++shThrow :: RMonadIO m => Exception -> m a+shThrow = lIO . throwIO++shCatch :: RMonadIO m => m a -> (Exception -> m a) -> m a+shCatch = snag++-- | Useful for debugging+shReport :: RMonadIO m => String -> m ()+shReport = lIO . hPutStrLn stderr++-- | make IORef available with SIO, so we may write tests that attempt+-- to leak handles and computations with handles via assignment++sNewIORef :: RMonadIO m => a -> m (IORef a)+sNewIORef = lIO . newIORef++sReadIORef :: RMonadIO m => IORef a -> m a+sReadIORef = lIO . readIORef++sWriteIORef :: RMonadIO m => IORef a -> a -> m ()+sWriteIORef r v = lIO $ writeIORef r v+++-- | Standard HList stuff...+--+class TypeCast   a b   | a -> b, b->a   where typeCast   :: a -> b+class TypeCast'  t a b | t a -> b, t b -> a where typeCast'  :: t->a->b+class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b+instance TypeCast'  () a b => TypeCast a b where typeCast x = typeCast' () x+instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast''+instance TypeCast'' () a a where typeCast'' _ x  = x+++class TypeCast2   (a :: * -> *) (b :: * -> *)   | a -> b, b->a+class TypeCast2'  t (a :: * -> *) (b :: * -> *) | t a -> b, t b -> a+class TypeCast2'' t (a :: * -> *) (b :: * -> *) | t a -> b, t b -> a+instance TypeCast2'  () a b => TypeCast2 a b+instance TypeCast2'' t a b => TypeCast2' t a b+instance TypeCast2'' () a a++
liboleg.cabal view
@@ -1,5 +1,5 @@ name:           liboleg-version:        0.1.0.2+version:        0.1.0.3 license:        BSD3 license-file:   LICENSE author:         Oleg Kiselyov@@ -36,6 +36,7 @@             System.IterateeM             System.LowLevelIO             System.RandomIO+            System.SafeHandles      ghc-options:             -funbox-strict-fields