packages feed

attempt (empty) → 0.0.0

raw patch · 7 files changed

+433/−0 lines, 7 filesdep +basedep +sybdep +transformerssetup-changed

Dependencies added: base, syb, transformers

Files

+ Control/Monad/Attempt.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+---------------------------------------------------------+--+-- Module        : Control.Monad.Attempt+-- Copyright     : Michael Snoyman+-- License       : BSD3+--+-- Maintainer    : Michael Snoyman <michael@snoyman.com>+-- Stability     : Unstable+-- Portability   : portable+--+---------------------------------------------------------++-- | Provide a monad transformer for the attempt monad, which allows the+-- reporting of errors using extensible exceptions.+module Control.Monad.Attempt+    ( AttemptT (..)+    , evalAttemptT+    , module Data.Attempt+    ) where++import Data.Attempt+import Control.Applicative+import Control.Monad+import Control.Monad.Trans++newtype AttemptT m v = AttemptT {+    runAttemptT :: m (Attempt v)+}++instance Monad m => Functor (AttemptT m) where+    fmap f = AttemptT . liftM (liftM f) . runAttemptT+instance Monad m => Applicative (AttemptT m) where+    pure = return+    (<*>) = ap+instance Monad m => Monad (AttemptT m) where+    return = AttemptT . return . Success+    (AttemptT mv) >>= f = AttemptT $+        mv >>= attempt (return . Failure) (runAttemptT . f)+instance Monad m => MonadAttempt (AttemptT m) where+    failure = AttemptT . return . Failure+    wrapFailure f (AttemptT mv) = AttemptT $ liftM (wrapFailure f) mv+instance MonadTrans AttemptT where+    lift = AttemptT . liftM Success where+instance MonadIO m => MonadIO (AttemptT m) where+    liftIO = AttemptT . liftM Success . liftIO where+instance Monad m => FromAttempt (AttemptT m) where+    fromAttempt = attempt failure return++-- | Instances of 'FromAttempt' specify a manner for embedding 'Attempt'+-- failures directly into the target data type. For example, the 'IO' instance+-- simply throws a runtime error. This is a convenience wrapper when you simply+-- want to use that default action.+--+-- So given a type 'AttemptT' 'IO' 'Int', this function will convert it to 'IO'+-- 'Int', throwing any exceptions in the original value.+evalAttemptT :: (Monad m, FromAttempt m)+             => AttemptT m v+             -> m v+evalAttemptT = join . liftM fromAttempt . runAttemptT where
+ Control/Monad/Attempt/Class.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Rank2Types #-}+---------------------------------------------------------+--+-- Module        : Control.Monad.Attempt.Class+-- Copyright     : Michael Snoyman+-- License       : BSD3+--+-- Maintainer    : Michael Snoyman <michael@snoyman.com>+-- Stability     : Unstable+-- Portability   : portable+--+---------------------------------------------------------++-- | Defines a type class for any monads which may report failure using+-- extensible exceptions.+module Control.Monad.Attempt.Class+    ( MonadAttempt (..)+    , StringException (..)+    ) where++import Control.Exception+import Data.Generics++-- | Any 'Monad' which may report failure using extensible exceptions. This+-- most obviously applies to the Attempt data type, but you should just as well+-- use this for arbitrary 'Monad's.+--+-- Usage should be straight forward: 'return' successes and 'failure' errors.+-- If you simply want to send a string error message, use 'failureString'.+-- Although tempting to do so, 'fail' is *not* used as a synonym for+-- 'failureString'; 'fail' should not be used at all.+--+-- Minimal complete definition: 'failure' and 'wrapFailure'.+class Monad m => MonadAttempt m where+    failure :: Exception e => e -> m v++    -- | Call 'failure' by wrapping the argument in a 'StringException'.+    failureString :: String -> m v+    failureString = failure . StringException++    -- | Wrap the failure value, if any, with the given function. This is+    -- useful in particular when you want all the exceptions returned from a+    -- certain library to be of a certain type, even if they were generated by+    -- a different library.+    wrapFailure :: Exception eOut+                => (forall eIn. Exception eIn => eIn -> eOut)+                -> m v+                -> m v++-- | A simple exception which simply contains a string. Note that the 'Show'+-- instance simply returns the contained string.+newtype StringException = StringException String+    deriving Typeable+instance Show StringException where+    show (StringException s) = s+instance Exception StringException
+ Data/Attempt.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE Rank2Types #-}+---------------------------------------------------------+--+-- Module        : Data.Attempt+-- Copyright     : Michael Snoyman+-- License       : BSD3+--+-- Maintainer    : Michael Snoyman <michael@snoyman.com>+-- Stability     : Unstable+-- Portability   : portable+--+-- A universal data type for computations which may fail.+---------------------------------------------------------++-- | A universal data type for computations which may fail. Errors are reported+-- using extensible exceptions. These exceptions are not explicitly stated; if+-- you want this kind of functionality, something like control-monad-exception+-- might be a more appropriate fit.+module Data.Attempt+    ( Attempt (..)+    , FromAttempt (..)+    , fa+    , attempt+    , makeHandler+    , AttemptHandler+    , module Control.Monad.Attempt.Class+    ) where++import qualified Control.Exception as E+import Control.Monad (ap)+import Control.Applicative+import Data.Generics+import Control.Monad.Attempt.Class++-- | Contains either a 'Success' value or a 'Failure' exception.+data Attempt v =+    Success v+    | forall e. E.Exception e => Failure e+    deriving (Typeable)++instance Show v => Show (Attempt v) where+    show (Success v) = "Success " ++ show v+    show (Failure e) = "Failure " ++ show e++instance Functor Attempt where+    fmap f (Success v) = Success $ f v+    fmap _ (Failure e) = Failure e+instance Applicative Attempt where+    pure = Success+    (<*>) = ap+instance Monad Attempt where+    return = Success+    (Success v) >>= f = f v+    (Failure e) >>= _ = Failure e+instance MonadAttempt Attempt where+    failure = Failure+    wrapFailure _ (Success v) = Success v+    wrapFailure f (Failure e) = Failure $ f e++-- | Any type which can be converted from an 'Attempt'. The included instances are your \"usual suspects\" for dealing with error handling. They include:+--+-- 'IO': For the IO instance, any exceptions in the 'Attempt' are thrown as+-- runtime exceptions.+--+-- 'Maybe': Returns 'Nothing' on 'Failure', or 'Just' on 'Success'.+--+-- List: Returns the empty list on 'Failure', or a singleton list on 'Success'.+--+-- 'Either' 'String': Returns 'Left' ('show' exception) on 'Failure', or 'Right' on+-- 'Success'.+--+-- 'Either' 'E.Exception': Returns 'Left' exception on 'Failure', or 'Right' on+-- 'Success'.+class FromAttempt a where+    fromAttempt :: Attempt v -> a v++-- | A shortcut for 'fromAttempt'.+fa :: FromAttempt a => Attempt v -> a v+fa = fromAttempt++instance FromAttempt IO where+    fromAttempt = attempt E.throwIO return+instance FromAttempt Maybe where+    fromAttempt = attempt (const Nothing) Just+instance FromAttempt [] where+    fromAttempt = attempt (const []) (: [])+instance FromAttempt (Either String) where+    fromAttempt = attempt (Left . show) Right+instance FromAttempt (Either E.SomeException) where+    fromAttempt = attempt (Left . E.SomeException) Right++-- | Process either the exception or value in an 'Attempt' to produce a result.+--+-- This function is modeled after 'maybe' and 'either'. The first argument must+-- accept any instances of 'E.Exception'. If you want to handle multiple types+-- of exceptions, see 'makeHandler'. The second argument converts the success+-- value.+attempt :: (forall e. E.Exception e => e -> b) -- ^ error handler+        -> (a -> b) -- ^ success handler+        -> Attempt a+        -> b+attempt _ f (Success v) = f v+attempt f _ (Failure e) = f e++-- | Convert multiple 'AttemptHandler's and a default value into an exception+-- handler.+--+-- This is a convenience function when you want to have special handling for a+-- few types of 'E.Exception's and provide another value for anything else.+makeHandler :: [AttemptHandler v] -> v -> (forall e. E.Exception e => e -> v)+makeHandler [] v _ = v+makeHandler (AttemptHandler h:hs) v e =+    case cast e of+        Nothing -> makeHandler hs v e+        Just e' -> h e'++-- | A simple wrapper value necesary due to the Haskell type system. Wraps a+-- function from a *specific* 'E.Exception' type to some value.+data AttemptHandler v = forall e. E.Exception e => AttemptHandler (e -> v)
+ Data/Attempt/Helper.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE DeriveDataTypeable #-}+---------------------------------------------------------+--+-- Module        : Data.Attempt.Helper+-- Copyright     : Michael Snoyman+-- License       : BSD3+--+-- Maintainer    : Michael Snoyman <michael@snoyman.com>+-- Stability     : Unstable+-- Portability   : portable+---------------------------------------------------------++-- | Replacements for standard functions to represent failure with a+-- 'MonadAttempt'.  Lots of inspiration taken from the "safe" package.+module Data.Attempt.Helper+    ( -- * Non-standard functions+      join+      -- * Exception types+    , KeyNotFound (..)+    , EmptyList (..)+    , CouldNotRead (..)+    , NegativeIndex (..)+      -- * Standard functions reimplemented+    , lookup+    , tail+    , init+    , head+    , last+    , read+    , at+    , assert+      -- * IO functions with exceptions handled+    , readFile+    ) where++import Prelude hiding (lookup, tail, init, head, last, read, readFile)+import qualified Prelude+import Data.Attempt+import Control.Monad.Attempt.Class+import Data.Generics+import qualified Control.Exception as E+import Control.Monad (liftM)+import Control.Monad.Trans++-- | This is not a simple translation of the Control.Monad.join function.+-- Instead, for 'Monad's which are instances of 'FromAttempt', it removes the+-- inner 'Attempt' type, reporting errors as defined in the 'FromAttempt'+-- instance.+--+-- For example, join (Just (failureString \"foo\")) == Nothing.+join :: (FromAttempt m, Monad m) => m (Attempt v) -> m v+join = (>>= fromAttempt)++-- | Exception type for the 'lookup' function.+data KeyNotFound k v = KeyNotFound k [(k, v)]+    deriving Typeable+instance Show k => Show (KeyNotFound k v) where+    show (KeyNotFound key _) = "Could not find requested key: " ++ show key+instance (Typeable k, Typeable v, Show k) => E.Exception (KeyNotFound k v)++lookup :: (Typeable k, Typeable v, Show k, Eq k, MonadAttempt m)+       => k+       -> [(k, v)]+       -> m v+lookup k m = maybe (failure $ KeyNotFound k m) return $ Prelude.lookup k m++-- | Exception type for functions which expect non-empty lists.+data EmptyList = EmptyList+    deriving (Show, Typeable)+instance E.Exception EmptyList++tail :: MonadAttempt m => [a] -> m [a]+tail [] = failure EmptyList+tail (_:rest) = return rest++init :: MonadAttempt m => [a] -> m [a]+init [] = failure EmptyList+init x = return $ Prelude.init x++head :: MonadAttempt m => [a] -> m a+head [] = failure EmptyList+head (x:_) = return x++last :: MonadAttempt m => [a] -> m a+last [] = failure EmptyList+last x = return $ Prelude.last x++-- | Report errors from the 'read' function.+newtype CouldNotRead = CouldNotRead String+    deriving (Typeable, Show)+instance E.Exception CouldNotRead++read :: (MonadAttempt m, Read a) => String -> m a+read s = case [x | (x,t) <- reads s, ("","") <- lex t] of+            [x] -> return x+            _ -> failure $ CouldNotRead s++-- | For functions which expect index values >= 0.+data NegativeIndex = NegativeIndex+    deriving (Typeable, Show)+instance E.Exception NegativeIndex+data OutOfBoundsIndex = OutOfBoundsIndex+    deriving (Typeable, Show)+instance E.Exception OutOfBoundsIndex++-- | Same as Prelude.!!. Name stolen from safe library.+at :: MonadAttempt m => [a] -> Int -> m a+at [] _ = failure OutOfBoundsIndex+at (x:_) 0 = return x+at (_:xs) n+    | n < 0 = failure NegativeIndex+    | otherwise = at xs $ n - 1++-- | Assert a value to be true. If true, returns the first value as a succss.+-- Otherwise, returns the second value as a failure.+assert :: (MonadAttempt m, E.Exception e)+       => Bool+       -> v+       -> e+       -> m v+assert b v e = if b then return v else failure e++-- | The standard readFile function with any 'E.IOException's returned as a+-- failure instead of a runtime exception.+readFile :: (MonadAttempt m, MonadIO m) => FilePath -> m String+readFile fp = do+    contents <- liftIO $ E.handle+                    (\e -> return $ Left (e :: E.IOException))+                    (liftM Right $ Prelude.readFile fp)+    case contents of+        Left e -> failure e+        Right v -> return v
+ LICENSE view
@@ -0,0 +1,25 @@+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2008, Michael Snoyman. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,11 @@+#!/usr/bin/env runhaskell++> module Main where+> import Distribution.Simple+> import System.Cmd (system)++> main :: IO ()+> main = defaultMainWithHooks (simpleUserHooks { runTests = runTests' })++> runTests' :: a -> b -> c -> d -> IO ()+> runTests' _ _ _ _ = system "runhaskell Test.hs" >> return ()
+ attempt.cabal view
@@ -0,0 +1,23 @@+name:            attempt+version:         0.0.0+license:         BSD3+license-file:    LICENSE+author:          Michael Snoyman <michael@snoyman.com>+maintainer:      Michael Snoyman <michael@snoyman.com>+synopsis:        Error handling using extensible exceptions outside the IO monad.+description:     Defines a data type, Attempt, which has a Success and Failure constructor. Failure contains an extensible exception.+category:        Data+stability:       unstable+cabal-version:   >= 1.2+build-type:      Simple+homepage:        http://github.com/snoyberg/attempt/tree/master++library+    build-depends:   base >= 4 && < 5,+                     syb,+                     transformers >= 0.1.4.0+    exposed-modules: Data.Attempt+                     Data.Attempt.Helper+                     Control.Monad.Attempt+                     Control.Monad.Attempt.Class+    ghc-options:     -Wall