2015-11-26 5 views
14

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) wandelt State (c,a) x-State (a,b,c) x
  • convert (0,1) wandelt State b x-State (a,b) x
  • convert (0,2,1,0) wandelt State (c,b) x zu State (a,b,c,d) x

Meine Fragen:

  1. 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 und g, entweder FG oder GF, wo F ist die Menge von Zustandswerten benötigt von f und G ist die Menge von Zustandswerten benötigt von g. Liege ich damit falsch? (Beachten Sie, dass mein Beispiel nicht diese Eigenschaft nicht erfüllt. Zum Beispiel G = {a, b} und H = {b, c}. Weder eine Teilmenge der anderen ist.)
  2. 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 
+5

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 –

+1

Das sollte mit abhängigen Typen einfach sein, aber nicht hasochistisch. – user3237465

+0

'Linse' + [' Vinyl'] (https://hackage.haskell.org/package/vinyl) sorgt für eine halbwegs anständige hasochistische Erfahrung. –

Antwort

9

Sie können es tun, indem aus lens-family oder lens Paket mit dem tuple-lenses Paket mit Zoom: die vereinfachte Art von zoom ist:

zoom :: Lens' s a -> State a x -> State s x 

So zoom führt eine Berechnung einen kleineren Zustand verwendet wird. Die Lens wird verwendet, um die Position des kleineren Status a im größeren Zustand s anzugeben.

Mit diesen beiden Paketen können Sie g, h und i laufen wie folgt:

f :: State (A,B,C) x 
f = do 
    zoom _12 g -- _12 :: Lens' (A,B,C) (A,B) 
    zoom _23 h -- _23 :: Lens' (A,B,C) (B,C) 
    zoom _13 i -- _13 :: Lens' (A,B,C) (A,C) 
+0

Danke, das habe ich mir vorgenommen :-) –

4

Wenn Sie wollen mit Tupeln nicht viel Aufhebens machen um Sie eine „noble“ Ansatz mit einem Rekord verwenden können . Es gibt einige ausgefallene Vorlage Haskell, um dies in der lens-Paket zu unterstützen, aber Sie können es auch manuell tun. Die Idee ist, mindestens eine Klasse für jedes Stück des Zustandes zu erzeugen:

class HasPoints s where 
    points :: Lens' s Int 

class ReadsPoints s where 
    getPoints :: Getter s Int 
    default getPoints :: HasPoints s => Getter s Int 
    getPoints = points 

class SetsPoints s where 
    setPoints :: Setter' s Int 
    ... 

Dann wird jede Funktion, die der Zustand manipuliert werden wie eine Art Signatur hat

fight :: (HasPoints s, ReadsHealth s) => StateT s Game Player 

Eine Aktion mit dieser bestimmten Signatur hat Voller Zugriff auf die Punkte und Lesezugriff auf die Gesundheit.

+0

Häufiger Anwendungsfall von mir: 'f :: State (A, A)()' und 'g :: State A()'. Innerhalb von "f" möchte ich 'g' entweder auf dem ersten oder dem zweiten Stück von' f's Zustand nennen können. 'g' sollte nicht wissen, welches dieser zwei Teile in es hineingereicht wird. 'g' sollte nicht einmal wissen, dass es Teil einer größeren Anwendung ist. Aber mit der noblen Lösung hätten 'f' und' g' den gleichen Zustandstyp (ein 'Game'-Wert) und verwenden die gleichen Getter/Setter, um auf jedes gegebene Stück dieses Zustands zuzugreifen, richtig? Während ich Informationen aus einer Funktion mit wenigen Verantwortlichkeiten verstecken kann, scheint es, dass meine Funktionen unbrauchbar sind. – Jordan