2016-01-19 20 views
7

Scalaz Stato monade di modify ha la seguente firma:trasformazioni dello Stato con una monade Stato informe

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

In questo modo lo Stato per essere sostituito da stato dello stesso tipo, che non funziona bene quando lo Stato comprende un valore senza forma come ad esempio Record il cui tipo cambia come vengono aggiunti nuovi campi. In questo caso quello che ci serve è:

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

cosa è un buon modo per adattarsi monade Stato di Scalaz di utilizzare lo stato informe in modo che si può usare Records a differenza, per esempio, il temuto Map[String, Any]?

Esempio:

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 

Aggiornamento:

Il codice completo per la risposta di Travis è here.

+0

Come dire 'def modificare [S, T] (f: S => T): Stato [T, Unità] = Stato ((s: S) => (f (s),()))'? – knutwalker

+0

@knutwalker stai suggerendo di estendere 'State' e sovraccaricare' modify'? – Sim

+0

No, basta scrivere questa funzione in qualsiasi punto del codice.Non è necessario estendere o sovraccaricare nulla. – knutwalker

risposta

8

State è un tipo alias per un tipo più generico IndexedStateT che è specificamente progettato per rappresentare le funzioni che cambiano il tipo di stato come i calcoli di stato:

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

Anche se non è possibile scrivere il modify[S, T] utilizzando State, è possibile con IndexedState (che è un altro tipo alias per IndexedStateT che fissa il tipo di effetto a Id):

import scalaz._, Scalaz._ 

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

È anche possibile utilizzare questo in for -comprehensions (che è sempre sembrato un po 'strano per me, dal momento che i cambiamenti di tipo monadici tra le operazioni, ma funziona):

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

E poi:

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

Nel tuo caso si potrebbe scrivere qualcosa del genere:

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),())) 

E poi:

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

(questo potrebbe non essere il modo migliore di factoring i pezzi di l'operazione di aggiornamento, ma dimostra come funziona l'idea di base.)

E 'anche interessante notare che se non si preoccupano che rappresenta il trasformazione stato come un calcolo dello stato, si può semplicemente utilizzare imap su qualsiasi vecchio State:

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

Questo non consente l'utilizzo di queste operazioni compositivo nello stesso modo, ma può essere tutto ciò che serve in alcune situazioni .

+2

La tua risposta, oltre ad essere elegante, contiene al suo interno un piccolo gioiello, che è la parte di 'addField' che estende un record usando una chiave e un valore. La combinazione del testimone e del campo [K] (a) 'non è ovvia senza leggere la fonte informe. – Sim

Problemi correlati