{-# LANGUAGE LambdaCase #-}
I have a bunch of functions that encode failure in various ways. For instance:
f :: A -> Bool returns False on failureg :: B -> Maybe B' returns Nothing on errorh :: C -> Either Error C' returns Left ... on error
I want to relate these operations in the same way as the Maybe monad, so the chain function needs to know if each function worked before moving on to the next. For this, I wrote this class:
class Fail a where isFail :: a -> Bool instance Fail () where isFail () = False instance Fail Bool where -- a isFail = not instance Fail (Maybe a) where -- b isFail = not . isJust instance Fail (Either ab) where -- c isFail (Left _) = True isFail _ = False
However, it is possible that functions that do not match exist:
f' :: A -> Bool returns True on failureg' :: B -> Maybe Error returns Just Error on failure ( Nothing on success)h' :: C -> Either C' Error returns Right ... on error
They could be fixed simply by wrapping them with functions that transform them, for example:
f'' = not . f' f'' = not . f' .g'' = (\case Nothing -> Right (); Just e -> Left e) . g'h'' = (\case Left c -> Right c; Right e -> Left e) . h'
However, the user of the chain function expects to combine f , g , h , f' , g' and h' and make them work. He did not know that the return type of the function should be transformed if he does not look at the semantics of each function that he combines, and check if they correspond to the dark Fail instances that he has in scope. This is tedious and too subtle for the average user to even notice, especially with the type of output that bypasses the user who must select the correct instances.
These functions were not created with knowledge of how they will be used. Therefore, I could create a data Result ab = Fail a | Success b type of data Result ab = Fail a | Success b data Result ab = Fail a | Success b and wrap around each function. For instance:
fR = (\case True -> Sucess (); False -> Fail ()) . ff'R = (\case False -> Sucess (); True -> Fail ()) . f'gR = (\case Just a -> Sucess a; Nothing -> Fail ()) . gg'R = (\case Nothing -> Sucess (); Just e -> Fail e) . g'hR = (\case Left e -> Fail e; Right a -> Sucess a) . hh'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'
However, this seems dirty. We simply confirm / explain how each of f , g , h , f' , g' and h' used in the context of the union function. Is there a more direct way to do this? I want to specify exactly which instance of the Fail class should be used for each function, i.e. (Using the names given for typeclass instances above), f β a , g β b , h β c and f' β a' , g' β b' , h' β c' for "invalid" functions, where a' , b' and c' defined as the following instances (which overlap the previous ones, so you will need to choose them somehow):
instance Fail Bool where -- a' isFail = id instance Fail (Maybe a) where -- b' isFail = isJust instance Fail (Either ab) where -- c' isFail (Right _) = True isFail _ = False
Of course, this does not have to be done through cool classes. Maybe there is a way to do this differently than using class tables?