What you probably want, instead of the State Monad, is the ST monad and mutable vectors.
Use the IO monad to read the bytes in from the class file.
bytes <- readFile myClassFile
use runST
to run your ST
monad calculation on the given bytes:
let result = runST $ transform bytes
The ST monad gives you access to mutable vectors, which are a lot like C or Java arrays. They're indexed by integers, and have O(1) lookup and modify.
transform :: [Char] -> ST s [Char]
transform bytes = do
mvec <- thaw $ fromList bytes
-- you can read a value at an index
val <- read mvec 0
-- and set a value at an index
write mvec 0 (val `xor` 0xff)
-- ...
-- turn it back into a list of bytes
vec <- freeze mvec
return $ toList vec
So just pass around the mvec
to all your functions (which must return a ST action), and you'll be able to do whatever you want to the bytes.
If you don't want to bother with passing it as an argument, consider using the ReaderT
monad transform to make the mvec
implicitily available to all your code.
transform bytes = do
-- ...
runReaderT other mvec
--- ...
other :: ReaderT (MVector s Char) (ST s) String
other = do
-- ...
-- grab the mvec when you need it
mvec <- ask
val <- lift $ read mvec 77
lift $ write mvec 77 (val * 363 - 28)
-- ...
return "Hi!"
Of course, this is all assuming you need random access to the bytes. If you don't... then you probably don't need an MVector
.
For example, if all you need to do is replace every instance of 0xDEADBEEF
with 0xCAFEBABE
, you could just use lists, no ST monad required:
let newBytes = intsToBytes . map (\i -> if i == 0xDEADBEEF then 0xCAFEBABE else i) $ bytesToInts bytes