Reputation: 131
I am writing a function in Haskell that takes in a Java class file, and writes another class file that is identical but contains some modifications. For this, I feel that I definitely need a state monad to at least hold the [Word8] that holds all of the bytes of the class file. However, after all of my research on State Monads in Haskell, I am still having trouble figuring out how to do this. Can anyone point me in the right direction? I would like to be able to have a [Word8] (or you know, any data type) that is in scope for all of the functions and that I can modify from functions. I understand this involves using something like state<-get ... put newstate
but I really don't know where to start with defining the monad and whatnot.
Thanks so much in advance!
Upvotes: 1
Views: 361
Reputation: 7001
I'm not sure you do want a State
monad. Depending on what kind of modifications you want to make, you are allowed to just pass the data you want to modify to every function that wants to modify it. State
is usually for situations where you are producing a value in addition to modifying state, i.e. when you are writing a lot of functions that look like s -> (s,a)
.
Try just a normal-function approach first. State
isn't magic, it just makes certain types of code easier to write quickly, concisely, and correctly. Everything you can do with it you can also do without it, it's just a bit more tedious.
Upvotes: 7
Reputation: 89053
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
Upvotes: 2