Simulieren interagierender statusbehafteter Objekte in Haskell

Ich schreibe gerade ein Haskell-Programm, in dem eine abstrakte Maschine simuliert wird, die über einen internen Status verfügt, Eingaben entgegennimmt und Ausgaben liefert. Ich weiß, wie man dies mit der staatlichen Monade umsetzt, was zu einem saubereren und besser verwaltbaren Code führt.

Mein Problem ist, dass ich nicht weiß, wie ich denselben Trick ausführen soll, wenn zwei (oder mehr) zustandsbehaftete Objekte miteinander interagieren. Im Folgenden gebe ich eine stark vereinfachte Version des Problems an und skizziere, was ich bisher habe.

Um dieser Frage willen nehmen wir an, dass der interne Status einer Maschine nur aus einem einzelnen Ganzzahlregister besteht, sodass der Datentyp @ is

data Machine = Register Int
        deriving (Show)

(Die tatsächliche Maschine verfügt möglicherweise über mehrere Register, einen Programmzeiger, einen Aufrufstapel usw. usw., aber machen wir uns diesbezüglich vorerst keine Sorgen.) Nach einem vorherige Frage Ich kann die Maschine mit der Zustandsmonade implementieren, damit ich ihren internen Zustand nicht explizit weitergeben muss. In diesem vereinfachten Beispiel sieht die Implementierung nach dem Import von @ so auControl.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

Das erlaubt mir Dinge wie @ zu schreib

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

runProgram = evalState program (Register 0)

Dies addiert 6 zum Register, subtrahiert dann 4 und gibt das Ergebnis zurück. Die Zustandsmonade verfolgt den internen Zustand der Maschine, so dass der "Programm" -Code ihn nicht explizit verfolgen muss.

In einem objektorientierten Stil in einer imperativen Sprache könnte dieser "Programm" -Code wie folgt aussehen:

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

enn ich in diesem Fall zwei Maschinen simulieren möchte, die miteinander interagieren, könnte ich schreibe

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

was setztmachine1er Status von @ wird auf 0 gesetzt und der Wert auf @ addiermachine2 's Zustand und Rückgabe des Ergebnisses.

Meine Frage ist einfach, wie man diese Art von imperativem Code in Haskell paradigmatisch schreibt. Ursprünglich dachte ich, ich müsste zwei Staatsmonaden verketten, aber nach einem Hinweis von Benjamin Hodgson in den Kommentaren wurde mir klar, dass ich es mit einer einzigen Staatsmonade tun sollte, bei der der Staat ein Tupel ist, das beide Maschinen enthäl

Das Problem ist, dass ich nicht weiß, wie ich das in einem netten, sauberen imperativen Stil umsetzen soll. Derzeit habe ich folgende, die funktioniert, aber unelegant und zerbrechlich ist:

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)

Der Typ Signaturinteraction :: State (Machine, Machine) Int ist eine nette direkte Übersetzung der Python-Funktionsdeklarationdef doInteraction(machine1, machine2):, aber der Code ist zerbrechlich, weil ich mithilfe von explizitem @ auf Threading-Status durch die Funktionen zurückgegriffen halet Bindungen. Daher muss ich jedes Mal einen neuen Namen eingeben, wenn ich den Status einer der Maschinen ändern möchte. Dies bedeutet, dass ich manuell nachverfolgen muss, welche Variable den aktuellsten Status darstellt. Bei längeren Interaktionen ist der Code wahrscheinlich fehleranfällig und schwer zu bearbeiten.

Ich gehe davon aus, dass das Ergebnis etwas mit Objektiven zu tun hat. Das Problem ist, dass ich nicht weiß, wie man eine monadische Aktion auf nur einer der beiden Maschinen ausführt. Objektive haben einen Operator<<~ in dessen Dokumentation steht "Eine monadische Aktion ausführen und das Ziel von Lens auf das Ergebnis festlegen", diese Aktion wird jedoch in der aktuellen Monade ausgeführt, in der der Status "@" laute(Machine, Machine) eher, alsMachine.

So ist an dieser Stelle meine Frage, wie kann ich das @ implementierinteraction Funktion oben in einem imperativeren / objektorientierten Stil, wobei Zustandsmonaden (oder ein anderer Trick) verwendet werden, um implizit die internen Zustände der beiden Maschinen zu verfolgen, ohne die Zustände explizit weitergeben zu müssen?

Schließlich ist mir klar, dass der Wunsch, objektorientierten Code in einer reinen funktionalen Sprache zu schreiben, ein Zeichen dafür sein kann, dass ich etwas falsch mache. Daher bin ich sehr offen dafür, eine andere Denkweise für das Problem der Simulation mehrerer zustandsbehafteter Dinge zu zeigen miteinander interagieren. Grundsätzlich möchte ich nur den "richtigen Weg" kennen, um diese Art von Problem in Haskell anzugehen.

Antworten auf die Frage(6)

Ihre Antwort auf die Frage