{-# LANGUAGE LambdaCase #-}
I have a bunch of functions which encode failure in various ways. For example:
f :: A -> BoolreturnsFalseon failureg :: B -> Maybe B'returnsNothingon failureh :: C -> Either Error C'returnsLeft ...on failure
I want to chain these operations in the same way as the Maybe monad, so the chaining function needs to know whether each function failed before proceeding to the next one. 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 a b) where -- c
isFail (Left _) = True
isFail _ = False
However, it's possible that functions that don't conform exist:
f' :: A -> BoolreturnsTrueon failureg' :: B -> Maybe ErrorreturnsJust Erroron failure (Nothingon success)h' :: C -> Either C' ErrorreturnsRight ...on failure
These could be remedied by simply wrapping them with functions that transform them, for example:
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 chaining function expects to be able to combine f,g,h,f',g', and h' and have them just work. He would not know that the return type of a function needs to be transformed unless he looks at the semantics of each function he's combining, and check if they match up with whatever Fail instances he has in scope. This is tedious and too subtle for the average user to even notice, especially with type inference bypassing the user having to choose the right instances.
These functions weren't created with knowledge of how they'd be used. So I could make a type data Result a b = Fail a | Success b and make wrappers around each function. For example:
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 feels dirty. What we're doing is just certifying / explaining how each of f,g,h,f',g', and h' are used in the context of the combining function. Is there are more direct way of doing this? What I want exactly is a way to say which instance of the Fail typeclass should be used for each function, i.e, (using the names given to the typeclass instances above), f → a, g → b, h → c, and f' → a', g' → b', h' → c' for the "invalid" functions, where a',b', and c' are defined as the following instances (which overlap the previous ones, so you'd need to be able to pick them by name somehow):
instance Fail Bool where -- a'
isFail = id
instance Fail (Maybe a) where -- b'
isFail = isJust
instance Fail (Either a b) where -- c'
isFail (Right _) = True
isFail _ = False
It doesn't necessarily have to by done via typeclasses though. Maybe there's some way to do this other than with typeclasses?
donotation) then you'll need to convert them all to a single type that you can then make a Monad instance for. You state that you want the type system to just figure out what a function means by failure without hints or context. In theory, I could have an infinite number of functions that each return a different Integer representing failure, how is the compiler supposed to know when a specific Integer is a failure? It only has a value as context. You can't expect the compiler to write your program for you, otherwise we'd all use Agda. - bheklilrf, and indicate a different set forf2, and so on. For example, in Python I could just make a global variable mapping functions to some equivalent of the typeclass instances. Then it would crash at runtime instead of compile time if there's no mapping from some function the user wants to use. But it would still be safer. - og_loc