On Mon, Oct 19, 2009 at 3:39 PM, Jose Iborra <pepeiborra@gmail.com> wrote:
You may want to take a look at anoother option in Hackage, the control-monad-exception package.

http://pepeiborra.github.com/control-monad-exception/

The control-monad-exception library provides the building blocks for

 * Explicitly Typed exceptions (checked or not)
 * which are composable
 * and even provide stack traces (experimental feature)

Jose,

Thank you very much, thinks looks like just the thing I was looking for. The only thing that slightly concerns me is the transformers dependency, but it's not a serious worry. I'd love to try out the package right now, but with hackage being down I can't get the dependencies yet ;). I'll try it out as soon as I can.
 



On 19/10/2009, at 01:00, Michael Snoyman wrote:

(Sorry, accidently took off cafe.)

On Mon, Oct 19, 2009 at 12:44 AM, Henning Thielemann <lemming@henning-thielemann.de> wrote:

On Mon, 19 Oct 2009, Michael Snoyman wrote:

Does the explicit-exception package provide what you need?

http://hackage.haskell.org/package/explicit-exception


I don't think so, but correct me if I'm wrong. I want to make it easy to chain together
computations which could fail in different ways. For example, something like this:

attemptReadInt :: String -> Attempt Int
attemptLookup :: String -> [(String, String)] -> Attempt String
attemptLookupInt :: String -> [(String, String)] -> Attempt Int
attemptLookupInt k m = attemptLookup k m >>= attemptReadInt

Now, in the explicit-exception package, I could- in this simple example- define
something like:

data MyErrors = KeyNotFound | InvalidInt


type Attempt = Exceptional MyErrors

True; that's what I meant by I could do this in my simple example.


But this solution would not scale.

You want to add other exceptions? The idea of my package is to make exceptions explicit in the type. Otherwise you would use extensible-exceptions. Or you could define MyErrors using an existential type.

Which is my point. I'm trying to provide a package for non-explicit exceptions. To compare to other programming languages, I think your package is providing the equivalent of Java checked exceptions, while mine is providing (safe) unchecked exceptions. I say safe because you still need to explicitly decide to turn an Attempt into a possible runtime exception which will bring down your program.

Defining MyErrors using an existential type would essentially recreate the entire attempt package; I don't see that purpose in everyone wanted unchecked exceptions needing to reinvent the wheel in non-compatible ways. If multiple libraries use attempt, they can easily have their possible-error-returning functions chain together safely.


I believe that control-monad-exception solves this tension between composability and explicit exceptions.
You can have explicit exceptions which are composable:

 > data DivideByZero = DivideByZero deriving (Show, Typeable)
 > data SumOverflow  = SumOverflow  deriving (Show, Typeable)

 > instance Exception DivideByZero
 > instance Exception SumOverflow

 > data Expr = Add Expr Expr | Div Expr Expr | Val Double

 > eval (Val x)     = return x
 > eval (Add a1 a2) = do
 >    v1 <- eval a1
 >    v2 <- eval a2
 >    let sum = v1 + v2
 >    if sum < v1 || sum < v2 then throw SumOverflow else return sum
 > eval (Div a1 a2) = do
 >    v1 <- eval a1
 >    v2 <- eval a2
 >    if v2 == 0 then throw DivideByZero else return (v1 / v2)

 GHCi infers the following types

 > :t eval
 >   eval :: (Throws DivideByZero l, Throws SumOverflow l) => Expr -> EM l Double

 > :t eval `catch` \ (e::DivideByZero) -> return (-1)
 >  .... :: Throws SumOverflow l => Expr -> EM l Double

 > :t runEM(eval `catch` \ (e::SomeException) -> return (-1))
 >  .... : Expr -> Double



Additionally, there's two immediate features I think I would miss from my package:

1) fail works properly, so an Attempt would be a valid monad response from people who
use that function.

As far as I understand, 'fail' is used/abused for reporting failed pattern matches in do notation. If a failed pattern match indicates a programming error, it should be a really error, and not something that must be handled at run-time.

That's a lot of very debateable statements you just made. It might be that it's strongly encouraged to only use fail for failed pattern matching, but in practice you could use it for any monadic failure. Also, there's nothing stopping a user from re-throwing pattern match exceptions received in an Attempt.

I am with Henning on 'fail'.
It must not be used as a replacement for throw, only for failed pattern matches which are programming errors and thus unchecked exceptions.

I'm not arguing how it *should* be used, I'm arguing how it *might* be used, and I think calling error is not appropriate. An unchcked exception which can be caught if necesary (I'm assuming that's what runEMParanoid does) sounds like that right thing.

Michael