[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