2015-06-02 16 views
13

Sto avvolgendo la mia testa attorno monade di stato. Gli esempi banali sono facili da capire. Ora sto passando a un caso reale in cui gli oggetti del dominio sono compositi. Ad esempio, con i seguenti oggetti di dominio (che non ha molto senso, solo pura esempio):Scala monade stato - combinazione di diversi tipi di stato

case class Master(workers: Map[String, Worker]) 
case class Worker(elapsed: Long, result: Vector[String]) 
case class Message(workerId: String, work: String, elapsed: Long) 

Considerando Worker come S tipi di State[S, +A] monade è abbastanza facile scrivere un paio di combinatori come questi:

type WorkerState[+A] = State[Worker, A] 
def update(message: Message): WorkerState[Unit] = State.modify { w => 
    w.copy(elapsed = w.elapsed + message.elapsed, 
      result = w.result :+ message.work) 
} 
def getWork: WorkerState[Vector[String]] = State { w => (w.result, w) } 
def getElapsed: WorkerState[Long] = State { w => (w.elapsed, w) } 
def updateAndGetElapsed(message: Message): WorkerState[Long] = for { 
    _ <- update(message) 
    elapsed <- getElapsed 
} yield elapsed 
// etc. 

Qual è il modo idiomatico di combinarli con i combinatori di stato Master? per esempio.

type MasterState[+A] = State[Master, A] 
def updateAndGetElapsedTime(message: Message): MasterState[Option[Long]] 

posso realizzare questo modo:

def updateAndGetElapsedTime(message: Message): MasterState[Option[Long]] = 
    State { m => 
     m.workers.get(message.workerId) match { 
      case None => (None, m) 
      case Some(w) => 
       val (t, newW) = updateAndGetElapsed(message).run(w) 
       (Some(t), m.copy(m.workers.updated(message.workerId, newW)) 
     } 
    } 

Quello che non mi piace è che devo eseguire manualmente la monade Stato all'interno dell'ultimo trasformatore. Il mio esempio del mondo reale è un po 'più coinvolto. Con questo approccio diventa rapidamente disordinato.

C'è altro modo idiomatico per eseguire questo tipo di aggiornamenti incrementali?

+0

Bella domanda! Ti riferisci ad alcune concrete implementazioni di 'State' come' scalaz'? – Odomontois

+0

Sembra decisamente un bell'esempio per l'uso di 'LensT', non vedo l'ora di vedere la risposta di un esperto. – Odomontois

risposta

8

È possibile farlo abbastanza bene combinando obiettivi e monade di stato. In primo luogo per la messa a punto (Ho modificato la vostra leggermente per farlo compilare con Scalaz 7.1):

case class Master(workers: Map[String, Worker]) 
case class Worker(elapsed: Long, result: Vector[String]) 
case class Message(workerId: String, work: String, elapsed: Long) 

import scalaz._, Scalaz._ 

type WorkerState[A] = State[Worker, A] 

def update(message: Message): WorkerState[Unit] = State.modify { w => 
    w.copy(
    elapsed = w.elapsed + message.elapsed, 
    result = w.result :+ message.work 
) 
} 

def getWork: WorkerState[Vector[String]] = State.gets(_.result) 
def getElapsed: WorkerState[Long] = State.gets(_.elapsed) 
def updateAndGetElapsed(message: Message): WorkerState[Long] = for { 
    _ <- update(message) 
    elapsed <- getElapsed 
} yield elapsed 

E ora per un paio di lenti di uso generale che ci permettono di guardare all'interno di un Master:

val workersLens: Lens[Master, Map[String, Worker]] = Lens.lensu(
    (m, ws) => m.copy(workers = ws), 
    _.workers 
) 

def workerLens(workerId: String): PLens[Master, Worker] = 
    workersLens.partial andThen PLens.mapVPLens(workerId) 

E allora siamo praticamente fatto:

def updateAndGetElapsedTime(message: Message): State[Master, Option[Long]] = 
    workerLens(message.workerId) %%= updateAndGetElapsed(message) 

Qui il %%= appena ci dice quale operazione di stato per eseguire una volta che abbiamo ingrandito l'worke appropriata tramite il nostro obiettivo

+0

Sto eseguendo le implementazioni di monade del mio povero uomo. Ho capito bene che per ottenere questo risultato ho bisogno di una implementazione di Lens e dell'integrazione di State con Lens (in particolare che %% = operation)? –

+0

Qual è la tua opinione sull'uso di Scalaz rispetto alle implementazioni manoscritte di monadi? –

+0

Se stai facendo questo genere di cose, ti suggerirei caldamente di usare Scalaz o gatti invece di lanciarti da solo. –