{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ExistentialQuantification #-} -- | Various functions for declaring new instances of Binary for types. module Util.BinaryUtils( mapWrite, -- :: HasBinary b m => (a -> b) -> (WriteBinary m -> a -> m ()) mapRead, -- :: (Monad m,HasBinary b m) => (b -> a) -> (ReadBinary m -> m a) mapWriteIO, -- :: (HasBinary b m,MonadIO m) -- => (a -> IO b) -> (WriteBinary m -> a -> m ()) mapReadIO, -- :: (HasBinary b m,MonadIO m) -- => (b -> IO a) -> (ReadBinary m -> m a) ArgMonad, -- A type for encoding a monadic action which requires an -- extra argument (of type "arg"). -- ArgMonad arg m -- is an instance of Monad (and Functor), if m is. -- -- ArgMonad is intended as a way of writing instances of Binary which -- require a bit of context. Thus you would write something like -- -- instance Monad m => HasBinary MyType1 (ArgMonad context m) where -- writeBinary wb (MyType1 v1 v2) = mkArgMonad -- (\ context -> -- do -- runArgMonad context (writeBinary rb v1) -- -- this is something which is automatically -- -- an instance of HasBinary for (ArgMonad context m) -- -- like the standard types. -- runArgMonad context (writeBinary rb (f v2 context)) -- -- this is something which needs to be changed by -- -- f, using context, to give a suitable instance. -- ) -- (and likewise for readBinary). -- -- -- Then if you want to encode MyType2, containing MyType1, and providing -- this context, you could write -- -- instance Monad m => HasBinary MyType2 m where -- writeBinary wb (MyType2 v3 v4) = -- do -- context <- ... -- writeBinary wb v3 -- encoding v3 doesn't need context -- runArgMonad context -- (writeBinary (writeBinaryToArgMonad wb) v4) -- -- encoding v4 does need context. mkArgMonad, -- :: (arg -> m a) -> ArgMonad arg m a toArgMonad, -- :: m a -> ArgMonad arg m a runArgMonad, -- :: arg -> ArgMonad arg m a -> m a writeBinaryToArgMonad, -- :: WriteBinary m -> WriteBinary (ArgMonad arg m) readBinaryToArgMonad, -- :: ReadBinary m -> ReadBinary (ArgMonad arg m) WrappedBinary(..), -- a wrapper for instances of HasBinary _ IO. hWriteWrappedBinary, -- :: Handle -> WrappedBinary -> IO () WrapBinary(..), -- more general wrapped for any monad. ) where import System.IO(Handle) -- GHC imports import Control.Monad.Trans -- our imports import Util.Binary -- ---------------------------------------------------------------------- -- Mapping HasBinary instances -- ---------------------------------------------------------------------- -- | Given a function which converts an (a) to something we can already -- convert to binary, return a 'writeBin' function to be used in -- instances of 'HasBinary' (a). mapWrite :: HasBinary b m => (a -> b) -> (WriteBinary m -> a -> m ()) mapWrite fn wb a = writeBin wb (fn a) -- | Given a function which converts something we can already read from -- binary to (a), return a 'readBin' function to be used in instances -- of 'HasBinary' (a). mapRead :: (Monad m,HasBinary b m) => (b -> a) -> (ReadBinary m -> m a) mapRead fn rb = do b <- readBin rb return (fn b) -- | Like 'mapWrite', but the conversion function is also allowed to use -- 'IO'. mapWriteIO :: (HasBinary b m,MonadIO m) => (a -> IO b) -> (WriteBinary m -> a -> m ()) mapWriteIO fn wb a = do b <- liftIO (fn a) writeBin wb b -- | LIke 'mapRead', but the conversion function is also allowed to use -- 'IO'. mapReadIO :: (HasBinary b m,MonadIO m) => (b -> IO a) -> (ReadBinary m -> m a) mapReadIO fn rb = do b <- readBin rb liftIO (fn b) -- ---------------------------------------------------------------------- -- Creating HasBinary instances that need extra information about their -- context -- ---------------------------------------------------------------------- -- | A monad which hides an additional value which the 'HasBinary' -- instances should be able to get at. This is used, for example, -- by "CodedValue", to make the 'View' available to instances. newtype ArgMonad arg m a = ArgMonad (arg -> m a) mkArgMonad :: (arg -> m a) -> ArgMonad arg m a mkArgMonad = ArgMonad toArgMonad :: m a -> ArgMonad arg m a toArgMonad act = ArgMonad (const act) writeBinaryToArgMonad :: WriteBinary m -> WriteBinary (ArgMonad arg m) writeBinaryToArgMonad = liftWriteBinary toArgMonad readBinaryToArgMonad :: ReadBinary m -> ReadBinary (ArgMonad arg m) readBinaryToArgMonad = liftReadBinary toArgMonad runArgMonad :: arg -> ArgMonad arg m a -> m a runArgMonad arg (ArgMonad fn) = fn arg instance Functor m => Functor (ArgMonad arg m) where fmap mapFn (ArgMonad fn) = let fn2 arg = fmap mapFn (fn arg) in ArgMonad fn2 instance Monad m => Monad (ArgMonad arg m) where (>>=) (ArgMonad fn1) getArgMonad = let fn arg = do v1 <- fn1 arg let (ArgMonad fn2) = getArgMonad v1 fn2 arg in ArgMonad fn return v = ArgMonad (const (return v)) fail s = ArgMonad (const (fail s)) instance MonadIO m => MonadIO (ArgMonad arg m) where liftIO act = ArgMonad (\ arg -> liftIO act) -- ---------------------------------------------------------------------- -- A wrapper for instances of Binary. This can be written, but not -- read (since we wouldn't know what type to decode). -- ---------------------------------------------------------------------- -- | A wrapper for instances of Binary. This can be written, but not -- read (since we wouldn't know what type to decode). data WrappedBinary = forall v . HasBinary v IO => WrappedBinary v -- | Write a 'WrappedBinary' hWriteWrappedBinary :: Handle -> WrappedBinary -> IO () hWriteWrappedBinary handle (WrappedBinary v) = hWrite handle v -- ---------------------------------------------------------------------- -- More generally we provide a wrapped type for each monad, and a way -- of writing it. Of course we have to leave the method for reading it -- undefined -- ---------------------------------------------------------------------- data WrapBinary m = forall v . HasBinary v m => WrapBinary v instance HasBinary (WrapBinary m) m where writeBin wb (WrapBinary v) = writeBin wb v readBin = error "BinaryUtils: can't read a general wrapped binary type"