Saturday, November 8, 2008

Constraints and Monads

As discussed last night, I've hit a problem with the type for decoding
SEQUENCE.

1. I have a function for decoding INTEGER.

> > (MonadError [Char] (t1 BG.BitGet), MonadTrans t1, Monad t) =>
> > t IntegerConstraint -> Bool -> t IntegerConstraint -> t (t1 BG.BitGet InfInteger)

As you can see the constraints live inside a monad as they may fail.

2. I have an auxialliary function for SEQUENCE. It takes a bit map and a
SEQUENCE and returns either an error or action (which when evaluated
will decode the bits). Note that the monad has become concrete as m =
Either String.

> > fromSequenceAuz :: (MonadTrans t, MonadError [Char] (t BG.BitGet)) => [Bool] -> Sequence a -> Either String (t BG.BitGet a)

In other words, this is

> > fromSequenceAuz :: (MonadTrans t, MonadError [Char] (t BG.BitGet)) => [Bool] -> Sequence a -> m (t BG.BitGet a)

but there'd probably need to be some extra constraints on m.

3. I have a function which returns (an action which when run returns)
the bit mask

> > Int -> BG.BitGet [Bool]

4. Here's where I have a problem. I can't "extract" the bit map from the
BG.BitGet monad (what I mean by extract is x <- bitMask 3 for example)
until I have extracted the action to run the decoding (fromSequenceAuz).
But I can't run this until I have the bit map.

Having spent all day yesterday on this, I think the answer is that we
should run the constraint monad before even attempting decoding whereas
at the moment I try to delay the constraint monad and evaluate it at the
last possible moment. I think this is the right approach. After all we
know the constraints are valid or invalid before we start decoding so
there's no point in doing any decoding work only to find out that the
constraints were invalid in the first place.

This will also make the code simpler. For example, the type signature
for decoding INTEGER would now be

> > (MonadError [Char] (t1 BG.BitGet), MonadTrans t1, Monad t) =>
> > IntegerConstraint -> Bool -> IntegerConstraint -> (t1 BG.BitGet InfInteger)

and there would be one less level of "do <- " in the function definition.

I'm inclined to do something like this (although I haven't quite thought
it through)

> > forget :: (MonadTrans t, MonadError [Char] (t BG.BitGet)) => Either String (t BG.BitGet a) -> t BG.BitGet a
> > forget (Left e) = throwError e
> > forget (Right x) = x

So the constraint error percolates through the (transformed) BitGet
monad and no decoding actually gets done.

A further thought: currently the error monad for throwError is just a
string. I think it would be better to have our own error monad with e.g.

data OurError = ConstraintError String | DecodeError String

then we can distinguish what sort of error caused the failure.

Thoughts?