Der Zustand meines Programms von drei Werten besteht, a
, b
und c
, von Typen A
, B
und C
. Verschiedene Funktionen benötigen Zugriff auf verschiedene Werte. Ich möchte Funktionen mit der State
Monade schreiben, so dass jede Funktion nur auf die Teile des Staates zugreifen kann, auf den sie zugreifen muss.Mix and Match Stateful Berechnungen innerhalb des Staates Monade
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 nenne g
innerhalb f
:
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
Das g'
Funktion ein hässliches Stück vorformulierten ist, die nichts als Brücke tut die Lücke zwischen den Typen (A, B, C)
und (A, B)
. Es ist im Grunde eine Version von g
, die auf einem 3-Tupel-Zustand arbeitet, aber das dritte Tupel-Element unverändert lässt. Ich bin auf der Suche nach einer Möglichkeit f
ohne diesen Text zu schreiben. Vielleicht so etwas wie folgt aus:
f = do
-- stuff
x <- convert (0,1,2) (g y)
-- more stuff
Wo convert (0,1,2)
eine Berechnung des Typs umwandelt State (a, b) x
eingeben State (a, b, c) x
. Ebenso für alle Arten a
, b
, c
, d
:
convert (2,0,1)
wandeltState (c,a) x
-State (a,b,c) x
convert (0,1)
wandeltState b x
-State (a,b) x
convert (0,2,1,0)
wandeltState (c,b) x
zuState (a,b,c,d) x
Meine Fragen:
- Gibt es eine bessere Lösung als Statuswerte in Tupel zu setzen? Ich dachte darüber nach, einen Monad-Transformator-Stack zu verwenden. Aber ich denke, dass nur dann, wenn für zwei beliebige Funktionen arbeitet
f
undg
, entwederF
⊆G
oderG
⊆F
, woF
ist die Menge von Zustandswerten benötigt vonf
undG
ist die Menge von Zustandswerten benötigt vong
. Liege ich damit falsch? (Beachten Sie, dass mein Beispiel nicht diese Eigenschaft nicht erfüllt. Zum BeispielG
={a, b}
undH
={b, c}
. Weder eine Teilmenge der anderen ist.) - Wenn es keinen besseren Weg, als Tupel, dann gibt es eine gute Möglichkeit, Vermeiden Sie das von mir erwähnte Beispiel? Ich bin sogar bereit, eine Datei mit einer Reihe von Boilerplate-Funktionen zu schreiben (siehe unten), solange die Datei automatisch einmal 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 die scheinbare Abhängigkeit von Template Haskell sind abschreckend. 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
Ich habe es selbst nicht benutzt, aber es klingt sehr ähnlich wie "Objektiv" ist "Zoom": http://hackage.haskell.org/package/lens-4.13/docs/Control-Lens-Zoom.html #v: zoom –
Das sollte mit abhängigen Typen einfach sein, aber nicht hasochistisch. – user3237465
'Linse' + [' Vinyl'] (https://hackage.haskell.org/package/vinyl) sorgt für eine halbwegs anständige hasochistische Erfahrung. –