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 +61/−0
- Control/Monad/Attempt/Class.hs +58/−0
- Data/Attempt.hs +123/−0
- Data/Attempt/Helper.hs +132/−0
- LICENSE +25/−0
- Setup.lhs +11/−0
- attempt.cabal +23/−0
+ 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