Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Supertypes in Haskell

Tags:

types

haskell

Apologies in advance if I use the wrong terminology here.

What is the idiomatic way in Haskell to generalize two or more types so that you can defer pattern-matching against them while avoiding boilerplate code?

To give a concrete example: In my application I want to pass on possible errors that can happen during the execution. These errors are from a different module, so I don't directly control them:

data ErrorCatA = WTFError String | OMGError String
data ErrorCatB = BadError Int | TerribleError Float

Now I want to pass some form of supertype of these error categories on so I can handle them like this:

handleError :: GenericError -> IO ()
handleError err =
    putStrLn $ case err of
        WTFError s -> "WTF?! " ++ s
        OMGError s -> "OMG?! " ++ s
        BadError i -> if i > 5 then "Really bad" else "Not that bad"
        TerribleError f -> "Terrible! " ++ show f

Is this possible?

I got closest by creating a wrapper type like this:

data GenericError = CatA ErrorCatA | CatB ErrorCatB
class GError a where
    wrap :: a -> GenericError

instance GError ErrorCatA where
    wrap = CatA

instance GError ErrorCatB where
    wrap = CatB

By doing this I can wrap all errors easily, as in

handleError $ wrap $ WTFError "Woopsie"

but I would need to change handleError to match against CatA (WTFError s) etc.

Is there a simpler or more idiomatic way of dealing with a scenario like this?

like image 920
passy Avatar asked May 14 '14 17:05

passy


2 Answers

Say you have the exception types

data HttpException  -- from http-client package
data MyCustomError = WTFError String | OMGError String
data AnotherError = BadError Int | TerribleError Float

And you wanted to handle each individually, but generically. Instead of writing a sum type around them as

data AllErrors = A HttpException | B MyCustomError | C AnotherError

What you really want is to handle each exception. So why not just do that? Write the functions

handleHttpError    :: HttpException -> IO ()
handleCustomError  :: MyCustomError -> IO ()
handleAnotherError :: AnotherError  -> IO ()

Then write a class

class HandledError e where
    handleError :: e -> IO ()

With

instance HandledError HttpException where
    handleError = handleHttpError

instance HandledError MyCustomError where
    handleError = handleCustomError

instance HandledError AnotherError where
    handleError = handleAnotherError

And just use handleError where needed. It isn't really any different than what you have, but now the logic for handling one kind of error isn't mixed in with the logic for handling another kind of error. Just think of the class level handleError as the same as your handleError . wrap.

like image 160
bheklilr Avatar answered Oct 24 '22 23:10

bheklilr


I'd go with creating a type class:

class GError a where
    errorMessage :: a -> String

and provide meaningful instances to it:

instance GError ErrorCatA where
    errorMessage (WTFError s) = "WTF?! " ++ s
    errorMessage (OMGError s) = "OMG?! " ++ s

instance GError ErrorCatB where
    errorMessage (BadError i)      = "Bad! " ++ show i
    errorMessage (TerribleError f) =  "Terrible! " ++ show f

and use it like:

handleError :: GError a => a -> IO ()
handleError = putStrLn . errorMessage

Live demo

Of course the GError instance is fully customizable. You can include any behavior that is both of ErrorCatA and ErrorCatB in your specific context.

like image 44
Shoe Avatar answered Oct 25 '22 00:10

Shoe