BruceBerry
BruceBerry

Reputation: 1186

How do I abstract this pattern in haskell?

Scenario: I have an interpreter that builds up values bottom-up from an AST. Certain nodes come with permissions -- additional boolean expressions. Permission failures should propagate, but if a node above in the AST comes with a permission, a success can recover the computation and stop the propagation of the error.

At first I thought the Error MyError MyValue monad would be enough: one of the members of MyError could be PermError, and I could use catchError to recover from PermError if the second check succeeds. However, MyValue is gone by the time I get to the handler. I guess there could ultimately be a way of having PermError carry a MyValue field so that the handler could restore it, but it would probably be ugly and checking for an exception at each step would defeat the concept of an exceptional occurrence.

I'm trying to think of an alternative abstraction. Basically I have to return a datatype Either AllErrorsExceptPermError (Maybe PermError, MyValue) or more simply (Maybe AllErrors, MyValue) (the other errors are unrecoverable and fit the error monad pretty well) and I'm looking for something that would save me from juggling the tuple around, since there seems to be a common pattern in how the operations are chained. My haskell knowledge only goes so far. How would you use haskell to your advantage in this situation?

While I write this I came up with an idea (SO is a fancy rubber duck): a Monad that that handles internally a type (a, b) (and ultimately returns it when the monadic computation terminates, there has to be some kind of runMyMonad), but lets me work with the type b directly as much as possible. Something like

data T = Pass | Fail | Nothing
instance Monad (T , b) where
  return v = (Nothing, v)
  (Pass, v) >>= g = let (r', v') = g v in (if r' == Fail then Fail else Pass, v')
  (Fail, v) >>= g = let (r', v') = g v in (if r' == Pass then Pass else Fail, v')
  (Nothing, _) >>= g = error "This should not have been propagated, all chains should start with Pass or Fail"

errors have been simplified into T, and the instance line probably has a syntax error, but you should get the idea. Does this make sense?

Upvotes: 3

Views: 207

Answers (1)

Ankur
Ankur

Reputation: 33637

I think you can use State monad for permissions and value calculation and wrap that inside ErrorT monad transformer to handle the errors. Below is such an example which shows the idea , here the calculation is summing up a list, permissions are number of even numbers in the list and error condition is when we see 0 in the list.

import Control.Monad.Error
import Control.Monad.State

data ZeroError = ZeroError String
              deriving (Show)

instance Error ZeroError where


fun :: [Int] -> ErrorT ZeroError (State Int) Int
fun [] = return 0
fun (0:xs) = throwError $ ZeroError "Zero found"
fun (x:xs) = do
  i <- get
  put $ (if even(x) then i+1 else i)
  z <- fun xs
  return $ x+z


main = f $ runState (runErrorT $ fun [1,2,4,5,10]) 0
       where
         f (Left e,evens) = putStr $ show e
         f (Right r,evens) = putStr $ show (r,evens)

Upvotes: 2

Related Questions