I've got a set of users, groups, and a mapping between users and groups. I have various functions that manipulate these sets, however one should not be able to add a user<->group mapping for a user that does not exist, nor remove a group which still has users as members, etc.
So basically I want these functions to throw "exceptions" that must be explicitly dealt with by the caller.
I first thought of returning something like this:
data Return r e = Success r | Exception e
And if the caller fails to pattern match against the Exception
, they'll hopefully get a compiler warning, or at the very least have an obvious runtime error when there is a problem.
Is this the best approach, and is there a pre-packaged solution that does this? Note I need to throw and catch "exceptions" in pure code, not the IO Monad.
The Haskell runtime system allows any IO action to throw runtime exceptions. Many common library functions throw runtime exceptions. There is no indication at the type level if something throws an exception. You should assume that, unless explicitly documented otherwise, all actions may throw an exception.
In Haskell either is used to represent the possibility of two values. Either is used to represent two values that can be correct or error. It has two constructors also which are named Left and Right. These constructors also represent and show some purpose either in Haskell.
On the one hand, an error is a programming mistake such as a division by zero, the head of an empty list, or a negative index. If we identify an error, we remove it. Thus, we don't handle errors, we simply fix them. In Haskell, we have error and undefined to cause such errors and terminate execution of the program.
Definition: An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions. When an error occurs within a method, the method creates an object and hands it off to the runtime system.
Yes, this is a good approach, and it's in the standard library: Return r e
is the same as Either e r
. You can even write code like you would using exceptions in IO
, too (i.e. without having to explicitly handle the errors at each step with pattern matching): the Monad
instance for Either
propagates the errors, just like the Maybe
monad does (but with the additional e
value in the case of an error). For example:
data MyError
= Oops String
| VeryBadError Int Int
mightFail :: T -> Either MyError Int
mightFail a = ...
foo :: T -> T -> Int -> Either MyError Int
foo a b c = do
x <- mightFail a
y <- mightFail b
if x == y
then throwError (VeryBadError x y)
else return (x + y + c)
If mightFail a
or mightFail b
returns Left someError
, then foo a b c
will, too; the errors are automatically propagated. (Here, throwError
is just a nice way of writing Left
, using the functions from Control.Monad.Error
; there's also catchError
to catch these exceptions.)
The Return r e
type that you are describing is exactly the standard type
data Either a b = Left a | Right b
You might want to use the so called "error monad" (a more suitable name is "exception monad") of the mtl package. (Alternatively, there's ExceptionT
in the monadLib package if you don't want to use mtl.) This allows you to do error handling in pure code by invoking throwError
and catchError
.
Here you can find an example that shows how to use it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With