Supertypes in Haskell

Sorry 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 to them while avoiding the pattern?

To give a concrete example: in my application I want to convey possible errors that may occur at runtime. These errors relate to another module, so I do not directly control them:

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

Now I want to convey some form of supertype of these error categories so that I can handle them as follows:

 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 it possible?

I got closest by creating this type of shell:

 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 

Through this, I can easily transfer all errors, as in

handleError $ wrap $ WTFError "Woopsie"

but I will need to change handleError to match CatA (WTFError s) , etc.

Is there an easier or more idiomatic way to solve this scenario?

+8
types haskell
source share
3 answers

Suppose you have exception types

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

And you wanted to process each separately, but in general. Instead of writing the type of amount 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 it? Write functions

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

Then write a class

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

FROM

 instance HandledError HttpException where handleError = handleHttpError instance HandledError MyCustomError where handleError = handleCustomError instance HandledError AnotherError where handleError = handleAnotherError 

And just use handleError where necessary. This is not exactly what you have, but now the logic for processing one kind of error does not mix with the logic for processing another error. Just think of the level of the handleError class as what your handleError . wrap handleError . wrap .

+7
source share

I would go with creating a class like:

 class GError a where errorMessage :: a -> String 

and provide meaningful instances for 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 GError fully customizable. You can include any behavior that is both ErrorCatA and ErrorCatB in your specific context.

+4
source share

This document explains how a Control.Exception is implemented. I think it can be called extensible sum-type (the top type for some arbitrary family of (sub) types, so it’s a β€œsuper type”, while a simple summary type like Either ab is not extensible, but still a supertype for a and b , a fixed family of subtypes). Prism from the lens library are linked, see Control.Lens.Prism and Control.Exception.Lens for use with exceptions.


Example with exceptions:

 {-# LANGUAGE DeriveDataTypeable, ExistentialQuantification #-} import Data.Typeable import Control.Exception data ErrorCatA = WTFError String | OMGError String deriving ( Show, Typeable ) data ErrorCatB = BadError Int | TerribleError Float deriving ( Show, Typeable ) instance Exception ErrorCatA instance Exception ErrorCatB handleError :: SomeException -> IO () handleError err = putStrLn $ case fromException err of Just (WTFError s) -> "WTF?! " ++ s Just (OMGError s) -> "OMG?! " ++ s _ -> case fromException err of Just (BadError i) -> if i > 5 then "Really bad" else "Not that bad" Just (TerribleError f) -> "Terrible! " ++ show f _ -> "SomeException is extensible, so..." -- ^ looks not so good, so data H a = forall e . Exception e => H (e -> a) match :: SomeException -> b -> [H b] -> b match e = foldr (\(H f) r -> maybe rf $ fromException e) -- then handleError' :: SomeException -> IO () handleError' err = putStrLn $ match err handleOther [H handleA, H handleB] where handleA (WTFError s) = "WTF?! " ++ s handleA (OMGError s) = "OMG?! " ++ s handleB (BadError i) = if i > 5 then "Really bad" else "Not that bad" handleB (TerribleError f) = "Terrible! " ++ show f handleOther = "SomeException is extensible, so..." -- ^ looks better main :: IO () main = do mapM_ (handleError . toException) [WTFError "...", OMGError "..."] mapM_ (handleError . toException) [BadError 0, TerribleError 0] handleError $ toException DivideByZero mapM_ (handleError' . toException) [WTFError "...", OMGError "..."] mapM_ (handleError' . toException) [BadError 10, TerribleError 0] handleError' $ toException DivideByZero 

or you can write an Exception -like class with SomeException -like for your own use (prisms can be added too).

+3
source share

All Articles