2016-01-19 22 views
7

Scalaz Staat Monade modify die folgende Signatur hat:Staat Transformationen mit einem unförmigen Staat Monade

def modify[S](f: S => S): State[S, Unit] 

Dies ermöglicht es dem Staat für Staat des gleichen Typs ersetzt werden, was nicht gut funktioniert, wenn der Zustand enthält ein formloser Wert wie Record, dessen Typ sich ändert, wenn neue Felder hinzugefügt werden. In diesem Fall, was wir brauchen, ist:

def modify[S, T](f: S => T): State[T, Unit] 

Was ist ein guter Weg Scalaz Staats Monade zu verwenden formlos Zustand anzupassen, so dass eine Datensätze im Gegensatz verwenden können, um, sagen wir, die gefürchteten Map[String, Any]?

Beispiel:

case class S[L <: HList](total: Int, scratch: L) 

def contrivedAdd[L <: HList](n: Int): State[S[L], Int] = 
    for { 
    a <- init 
    _ <- modify(s => S(s.total + n, ('latestAddend ->> n) :: s.scratch)) 
    r <- get 
    } yield r.total 

Update:

Der vollständige Code für Antwort Travis ist here.

+0

Wie wäre es mit 'def modify [S, T] (f: S => T): Zustand [T, Einheit] = Zustand ((s: S) => (f (s),()))'? – knutwalker

+0

@knutwalker schlagen Sie vor, "State" zu erweitern und "Modify" zu überladen? – Sim

+0

Nein, schreibe einfach diese Funktion irgendwo in deinen Code.Es besteht keine Notwendigkeit, etwas zu erweitern oder zu überlasten. – knutwalker

Antwort

8

State ist eine Art Alias ​​für eine generische Art IndexedStateT, die speziell die Funktionen darstellen, die den Zustand Typ als Zustands Berechnungen ändern:

type StateT[F[_], S, A] = IndexedStateT[F, S, S, A] 
type State[S, A] = StateT[Id, S, A] 

Es ist zwar nicht möglich, Ihre modify[S, T] mit State zu schreiben, ist es möglich, mit IndexedState (der anderen Art Alias ​​für IndexedStateT ist, die den Effekt Typen Id fixiert):

import scalaz._, Scalaz._ 

def transform[S, T](f: S => T): IndexedState[S, T, Unit] = 
    IndexedState(s => (f(s),())) 

Sie können dies auch in for -comprehensions verwenden (das ist immer ein wenig seltsam schien mir hat, da die monadische Typänderungen zwischen den Operationen, aber es funktioniert):

val s = for { 
    a <- init[Int]; 
    _ <- transform[Int, Double](_.toDouble) 
    _ <- transform[Double, String](_.toString) 
    r <- get 
} yield r * a 

Und dann:

scala> s(5) 
res5: scalaz.Id.Id[(String, String)] = (5.0,5.05.05.05.05.0) 

In Ihrem Fall könnten Sie so etwas schreiben:

import shapeless._, shapeless.labelled.{ FieldType, field } 

case class S[L <: HList](total: Int, scratch: L) 

def addField[K <: Symbol, A, L <: HList](k: Witness.Aux[K], a: A)(
    f: Int => Int 
): IndexedState[S[L], S[FieldType[K, A] :: L], Unit] = 
    IndexedState(s => (S(f(s.total), field[K](a) :: s.scratch),())) 

Und dann:

def contrivedAdd[L <: HList](n: Int) = for { 
    a <- init[S[L]] 
    _ <- addField('latestAdded, n)(_ + n) 
    r <- get 
} yield r.total 

(Dies kann nicht der beste Weg, um die Stücke des Aktualisierungsvorganges von Factoring, aber es wird gezeigt, wie die Grundidee funktioniert.)

Es ist auch erwähnenswert, dass, wenn Sie nicht über das Darstellen der egal Zustandstransformation als Zustandsberechnung, können Sie einfach imap verwenden, um auf alle alten State:

init[S[HNil]].imap(s => 
    S(1, field[Witness.`'latestAdded`.T](1) :: s.scratch) 
) 

Diese Sie diese Operationen kompositorisch auf die gleiche Art und Weise zu verwenden, ist es nicht möglich, aber es kann alles, was Sie brauchen in einigen Situationen sein .

+2

Ihre Antwort ist nicht nur elegant, sondern enthält auch ein hübsches kleines Juwel, das ein Stück von 'addField' ist, das einen Datensatz mit einem Schlüssel und einem Wert erweitert. Die Kombination des Zeugen und des Feldes [K] (a) ist nicht offensichtlich, ohne die formlose Quelle zu lesen. – Sim