2015-06-02 23 views
13

Owijam głowę wokół Monady stanu. Proste przykłady są łatwe do zrozumienia. Przechodzę teraz do rzeczywistego przypadku, w którym obiekty domeny są złożone. Na przykład, z następujących obiektów domeny (nie ma większego sensu, po prostu zwykły przykład):Moneta Scala State - łącząca różne typy stanów

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

Zważywszy Worker jak S typów w State[S, +A] monady to dość łatwe do napisania kilku kombinatorów, takie jak:

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. 

Jaki jest idiomatyczny sposób łączenia ich z kombinatorami stanu Master? na przykład

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

mogę wdrożyć to tak:

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)) 
     } 
    } 

Co mi się nie podoba to, że będę musiał ręcznie uruchomić monady State wewnątrz ostatniej transformatora. Mój przykład z prawdziwego świata jest nieco bardziej zaangażowany. Dzięki takiemu podejściu szybko się brudzi.

Czy istnieje bardziej idiomatyczny sposób uruchamiania tego rodzaju przyrostowych aktualizacji?

+0

Ładne pytanie! Czy odwołujesz się do jakiejś konkretnej implementacji 'State', takiej jak' scalaz'? – Odomontois

+0

Zdecydowanie wygląda na ładny przykład użycia 'LensT', nie może się doczekać odpowiedzi eksperta. – Odomontois

Odpowiedz

8

Można to zrobić całkiem ładnie, łącząc soczewki i monadę państwową. Pierwszy dla konfiguracji (mam edytować swoje lekko zmusić go do kompilacji z 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 

a teraz przez kilka obiektywów ogólnego przeznaczenia, które pozwalają nam spojrzeć wewnątrz 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) 

A potem mamy w zasadzie zrobione:

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

Tutaj %%= tylko mówi nam, co operacja stan wykonywać raz mamy powiększony do odpowiedniego worke r za pomocą naszego obiektywu.

+0

Używam implementacji monady mojego biednego człowieka. Czy rozumiem, że aby to zadziałało, potrzebuję implementacji obiektywu i integracji stanu z obiektywem (w szczególności %% = operacja)? –

+0

Również, w jaki sposób używasz Scalaz vs odręczne implementacje monad? –

+0

Jeśli robisz coś takiego, zdecydowanie sugerowałbym używanie Scalaza lub kotów zamiast tarzania się. –