Simulando objetos com estado de interação no Haskell

Atualmente, estou escrevendo um programa Haskell que envolve a simulação de uma máquina abstrata, que possui estado interno, recebe entrada e fornece saída. Eu sei como implementar isso usando a mônada do estado, o que resulta em um código muito mais limpo e mais gerenciável.

Meu problema é que não sei fazer o mesmo truque quando tenho dois (ou mais) objetos com estado interagindo uns com os outros. Abaixo, dou uma versão altamente simplificada do problema e esboço o que tenho até agora.

Para fins de pergunta, vamos assumir que o estado interno de uma máquina consiste apenas em um único registro inteiro, para que seu tipo de dados seja

data Machine = Register Int
        deriving (Show)

(A máquina real pode ter vários registros, um ponteiro de programa, uma pilha de chamadas etc. etc., mas não vamos nos preocupar com isso por enquanto.)pergunta anterior Eu sei como implementar a máquina usando a mônada do estado, para que eu não precise transmitir explicitamente seu estado interno. Neste exemplo simplificado, a implementação fica assim, depois de importarControl.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

Isso me permite escrever coisas como

program :: State Machine Int
program = do
        addToState 6
        addToState (-4)
        getValue

runProgram = evalState program (Register 0)

Isso adiciona 6 ao registro, subtrai 4 e retorna o resultado. A mônada de estado controla o estado interno da máquina, para que o código do "programa" não precise rastreá-lo explicitamente.

No estilo orientado a objeto em uma linguagem imperativa, esse código de "programa" pode parecer

def runProgram(machine):
    machine.addToState(6)
    machine.addToState(-4)
    return machine.getValue()

Nesse caso, se eu quiser simular duas máquinas interagindo umas com as outras, devo escrever

def doInteraction(machine1, machine2):
    a = machine1.getValue()
    machine1.addToState(-a)
    machine2.addToState(a)
    return machine2.getValue()

que definemachine1do estado para 0, adicionando seu valor aomachine2estado e retornando o resultado.

Minha pergunta é simplesmente: qual é a maneira paradigmática de escrever esse tipo de código imperativo em Haskell? Originalmente, pensei que precisava encadear duas mônadas de estado, mas, depois de uma dica de Benjamin Hodgson nos comentários, percebi que deveria ser capaz de fazê-lo com uma única mônada de estado, onde o estado é uma tupla contendo as duas máquinas.

O problema é que eu não sei como implementar isso em um bom estilo imperativo e limpo. Atualmente, tenho o seguinte, que funciona, mas é deselegante e frágil:

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)

A assinatura do tipointeraction :: State (Machine, Machine) Int é uma boa tradução direta da declaração da função Pythondef doInteraction(machine1, machine2):, mas o código é frágil porque recorri ao estado de segmentação por meio das funções usando explícitalet ligações. Isso exige que eu introduza um novo nome toda vez que eu quiser alterar o estado de uma das máquinas, o que, por sua vez, significa que eu preciso acompanhar manualmente qual variável representa o estado mais atualizado. Para interações mais longas, é provável que isso torne o código propenso a erros e difícil de editar.

Espero que o resultado tenha algo a ver com lentes. O problema é que não sei como executar uma ação monádica em apenas uma das duas máquinas. Lentes tem um operador<<~ cuja documentação diz "Executar uma ação monádica e defina o destino do Lens como resultado", mas essa ação é executada na mônada atual, onde o estado é do tipo(Machine, Machine) ao invés deMachine.

Então, neste momento, minha pergunta é: como posso implementar ointeraction funcionar acima em um estilo mais imperativo / orientado a objeto, usando mônadas de estado (ou algum outro truque) para acompanhar implicitamente os estados internos das duas máquinas, sem ter que passar os estados explicitamente?

Por fim, percebo que querer escrever código orientado a objetos em uma linguagem funcional pura pode ser um sinal de que estou fazendo algo errado, por isso estou muito aberto a mostrar outra maneira de pensar sobre o problema de simular várias coisas com estado interagindo um com o outro. Basicamente, eu só quero saber o "caminho certo" para abordar esse tipo de problema em Haskell.

questionAnswers(3)

yourAnswerToTheQuestion