Mischen und vergleichen Sie zustandsbezogene Berechnungen innerhalb der Zustandsmonade

Der Status meines Programms besteht aus drei Werten:a, b, undc, von TypenA, B, undC. Verschiedene Funktionen benötigen Zugriff auf verschiedene Werte. Ich möchte Funktionen mit dem @ schreibState monad, sodass jede Funktion nur auf die Teile des Status zugreifen kann, auf die sie zugreifen muss.

Ich habe vier Funktionen der folgenden Typen:

f :: State (A, B, C) x
g :: y -> State (A, B) x
h :: y -> State (B, C) x
i :: y -> State (A, C) x

Hier ist, wie ich @ anrug innerhalbf:

f = do
    -- some stuff
    -- y is bound to an expression somewhere in here
    -- more stuff
    x <- g' y
    -- even more stuff

    where g' y = do
              (a, b, c) <- get
              let (x, (a', b')) = runState (g y) (a, b)
              put (a', b', c)
              return x

Dasg' function ist ein hässliches Stück Heizplatte, das nichts anderes tut, als die Lücke zwischen den Typen zu schließen(A, B, C) und(A, B). Es ist im Grunde eine Version vong, das mit einem 3-Tupel-Status arbeitet, aber das 3. Tupel-Element unverändert lässt. Ich suche einen Weg, um @ zu schreibf ohne diese Kesselplatte. Vielleicht so etwas:

f = do
    -- stuff
    x <- convert (0,1,2) (g y)
    -- more stuff

Woconvert (0,1,2) konvertiert eine Berechnung vom TypState (a, b) x tippenState (a, b, c) x. Ebenso für alle Artena, b, c, d:

convert (2,0,1) konvertiertState (c,a) x zuState (a,b,c) xconvert (0,1) konvertiertState b x zuState (a,b) xconvert (0,2,1,0) konvertiertState (c,b) x zuState (a,b,c,d) x

Meine Fragen

Gibt es eine bessere Lösung, als Zustandswerte in Tupel zu schreiben? Ich dachte darüber nach, einen Monadentransformator-Stack zu verwenden. Allerdings denke ich, dass das nur funktioniert, wenn für zwei beliebige Funktionenf undg, entwederFG oderGF, woF ist die Menge von Statuswerten, die von @ benötigt werdf undG ist die Menge von Statuswerten, die von @ benötigt werdg. Liege ich da falsch (Beachten Sie, dass mein Beispiel diese Eigenschaft nicht erfüllt. Beispiel:G = {a, b} undH = {b, c}. Weder ist eine Teilmenge der anderen.)Wenn es keinen besseren Weg gibt als Tupel, gibt es dann einen guten Weg, das erwähnte Boilerplate zu umgehen? Ich bin sogar bereit, eine Datei mit einer Reihe von Boilerplate-Funktionen (siehe unten) zu schreiben, sofern die Datei einmal automatisch generiert und dann vergessen werden kann. Gibt es einen besseren Weg? (Ich habe über Objektive gelesen, aber ihre Komplexität, hässliche Syntax, enorme Menge an unnötigen Funktionen und das scheinbare Vertrauen in Template Haskell sind abstoßend. Ist das ein Missverständnis von mir? Können Objektive mein Problem auf eine Weise lösen, die diese Probleme vermeidet ?)

(Die Funktionen, die ich erwähnte, würden ungefähr so aussehen.)

convert_0_1_2 :: State (a, b) x -> State (a, b, c) x
convert_0_1_2 f = do
    (a, b, c) <- get
    let (x, (a', b')) = runState f (a, b)
    put (a', b', c)
    return x

convert_0_2_1_0 :: State (c, b) x -> State (a, b, c, d) x
convert_0_2_1_0 f = do
    (a, b, c, d) <- get
    let (x, (b', c')) = runState f (b, c)
    put (a, b', c', d)
    return x

Antworten auf die Frage(4)

Ihre Antwort auf die Frage