
Andrew Coppin wrote:
data Writer x instance Monad Writer run_writer :: Writer () -> ByteString write1 :: Bool -> Writer () write8 :: Word8 -> Writer () write16 :: Word16 -> Writer () write32 :: Word32 -> Writer () write64 :: Word64 -> Writer () writeN :: Word8 -> Word32 -> Writer ()
data Reader x instance Monad Reader run_reader :: Reader x -> ByteString -> x is_EOF :: Reader Bool read1 :: Reader Bool read8 :: Reader Word8 read16 :: Reader Word16 read32 :: Reader Word32 read64 :: Reader Word64 readN :: Word8 -> Reader Word32
Next I decided to write an LZW compressor. Here's what I came up with:
data EncodeLZW symbol eLZW_start :: (Ord symbol) => EncodeLZW symbol eLZW_encode :: (Ord symbol) => EncodeLZW symbol -> symbol -> Writer (EncodeLZW symbol) eLZW_end :: (Ord symbol) => EncodeLZW symbol -> Writer ()
data DecodeLZW symbol dLZW_start :: DecodeLZW symbol dLZW_decode :: DecodeLZW symbol -> Reader (DecodeLZW symbol, symbol)
Suddenly it seems very obvious to me... I built a monad to allow writing bits to a ByteString, so why not make another monad for writing symbols to an LZW-compressed ByteString? So... run a monad on top of another monad. Can that be done? Notionally it should be possible. I mean, thinking about it, what's the difference between a Writer and an EncodeLZW? One writes bits using some internal state, the other writes symbols using a more complex algorithm and internal state. Heck, if I augment Writer slightly so that the user can carry along some arbitrary state of their own, then I can just do something like newtype EncodeLZW symbol x = Wrap (Writer (StateLZW symbol) x) deriving Monad Now I automatically have Wrap :: Writer (StateLZW symbol) x -> EncodeLZW symbol x as my magical lifting operator. Now the caller can't use any Writer functions (because they all have the wrong type) and can only use the LZW writing functions I provide. I can even write a class - something like class Encoder e s where encode :: symbol -> e symbol () run_encoder :: e symbol () -> Writer s () where the "run_encoder" thing is actually just a typecast. (I'm doing is this way so you could possibly run another, different, encoder feeding to the same sink. If I returned an actual ByteString you'd be prevented from doing that.) So far, this only appears to require GeneralizedNewtypeDeriving and MultiParamTypeClasses, so we should be golden... ...unless somebody else has a better idea?