I'm currently writing a Haskell program that involves simulating an abstract machine, which has internal state, takes input and gives output. I know how to implement this using the state monad, which results in much cleaner and more manageable code.
My problem is that I don't know how to pull the same trick when I have two (or more) stateful objects interacting with one another. Below I give a highly simplified version of the problem and sketch out what I have so far.
For the sake of this question, let's assume a machine's internal state consists only of a single integer register, so that its data type is
data Machine = Register Int
deriving (Show)
(The actual machine might have multiple registers, a program pointer, a call stack etc. etc., but let's not worry about that for now.) After a previous question I know how to implement the machine using the state monad, so that I don't have to explicitly pass its internal state around. In this simplified example the implementation looks like this, after importing Control.Monad.State.Lazy
:
addToState :: Int -> State Machine ()
addToState i = do
(Register x) <- get
put $ Register (x + i)
getValue :: State Machine Int
getValue = do
(Register i) <- get
return i
This allows me to write things like
program :: State Machine Int
program = do
addToState 6
addToState (-4)
getValue
runProgram = evalState program (Register 0)
This adds 6 to the register, then subtracts 4, then returns the result. The state monad keeps track of the machine's internal state so that the "program" code doesn't have to explicitly track it.
In object oriented style in an imperative language, this "program" code might look like
def runProgram(machine):
machine.addToState(6)
machine.addToState(-4)
return machine.getValue()
In that case, if I want to simulate two machines interacting with each other I might write
def doInteraction(machine1, machine2):
a = machine1.getValue()
machine1.addToState(-a)
machine2.addToState(a)
return machine2.getValue()
which sets machine1
's state to 0, adding its value onto machine2
's state and returning the result.
My question is simply, what is the paradigmatic way to write this kind of imperative code in Haskell? Originally I thought I needed to chain two state monads, but after a hint by Benjamin Hodgson in the comments I realised I should be able to do it with a single state monad where the state is a tuple containing both machines.
The problem is that I don't know how to implement this in a nice clean imperative style. Currently I have the following, which works but is inelegant and fragile:
interaction :: State (Machine, Machine) Int
interaction = do
(m1, m2) <- get
let a = evalState (getValue) m1
let m1' = execState (addToState (-a)) m1
let m2' = execState (addToState a) m2
let result = evalState (getValue) m2'
put $ (m1',m2')
return result
doInteraction = runState interaction (Register 3, Register 5)
The type signature interaction :: State (Machine, Machine) Int
is a nice direct translation of the Python function declaration def doInteraction(machine1, machine2):
, but the code is fragile because I resorted to threading state through the functions using explicit let
bindings. This requires me to introduce a new name every time I want to change the state of one of the machines, which in turn means I have to manually keep track of which variable represents the most up-to-date state. For longer interactions this is likely to make the code error-prone and hard to edit.
I expect that the result will have something to do with lenses. The problem is that I don't know how to run a monadic action on only one of the two machines. Lenses has an operator <<~
whose documentation says "Run a monadic action, and set the target of Lens to its result", but this action gets run in the current monad, where the state is type (Machine, Machine)
rather than Machine
.
So at this point my question is, how can I implement the interaction
function above in a more imperative / object-oriented style, using state monads (or some other trick) to implicitly keep track of the internal states of the two machines, without having to pass the states around explicitly?
Finally, I realise that wanting to write object oriented code in a pure functional language might be a sign that I'm doing something wrong, so I'm very open to being shown another way to think about the problem of simulating multiple stateful things interacting with each other. Basically I just want to know the "right way" to approach this sort of problem in Haskell.
See Question&Answers more detail:
os