I am having trouble defining an interface for restoring the state and undoing the writes upon failure for a monad transformer.
I have a transformer FileT
that wraps file Handles
, a class Try
that restores the state and discards the writes from putText
upon failure, and a class PutText
that is like MonadWriter
with only tell
to accepts data to write to file.
newtype FileT (mode :: IOMode) text m a = FileT (ReaderT (FilePath, Handle) m a)
class Try m where try :: m a -> m a
class PutText text m where putText :: text -> m ()
This definition of Try
works well with most monad transformers, but I have found an edge case, FileT AppendMode
, that it has low compatibility with. Unlike the other IOModes, AppendMode cannot seek and the text
must be buffered until success. I have come up with following bad solutions.
- Redefine
Try
to
This is terrible since it makes composition is very hard because: constraints must be defined for bothclass Try m n | n -> m where try :: m a -> n a -- writer buffers the text until success instance Try (FileT AppendMode text (WriterT text m)) (FileT AppendMode text m)
m
andn
, andtry
is going to be used inm
which exacerbates the previous problem that now has a new monad for everytry
used. - Use the original interface and define the transformer
FileAppendT
with specialized instances forTry
andPutText
This is better for composition but adds adds overhead of having a null check every time-- state holds the text to putText while in try -- the list is a stack for recursive trys newtype FileAppendT text m a = FileAppendT (FileT AppendMode text (StateT [text] m) a) -- add a new empty text to the top of the stack -- run the given computation -- pop the text off -- write it to file if the stack is now empty -- or append it to the text at the top of the stack instance (Try m, Monoid text) => Try (FileAppendT text m) where try x = do modify (mempty :) y <- try x get >>= \case [z] -> FileAppendT (putText z) *> put [] z:zs -> put zs *> putText z return y instance (PutText text m, Semigroup text) => PutText text (FileAppendT text m) where putText x = get >>= \case [] -> FileAppendT $ putText x y:ys -> put $ y <> x : ys
putText
is called.
Is there a better way to handle this edge case?