[Haskell-cafe] Re: Improvements [Architecturally flawed]

Andrew Coppin andrewcoppin at btinternet.com
Fri Jul 11 14:23:05 EDT 2008

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?

More information about the Haskell-Cafe mailing list