Подтвердить что ты не робот

Scala Государственная монада - объединение разных типов состояний

Я обнимаю голову над государственной монадой. Тривиальные примеры легко понять. Теперь я перехожу к реальному миру, когда объекты домена являются составными. Например, со следующими объектами домена (они не имеют большого смысла, просто пример):

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

Учитывая Worker как S типы в State[S, +A] monad, довольно легко написать несколько комбинаторов, подобных этим:

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.

Каков идиоматический способ объединить их с комбинаторами состояний Master? например.

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

Я могу реализовать это так:

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

Мне не нравится, что мне нужно вручную запустить государственную монаду внутри последнего трансформатора. Мой пример с реальным миром немного связан. При таком подходе он быстро становится беспорядочным.

Есть ли более идиоматический способ запуска такого рода инкрементных обновлений?

4b9b3361

Ответ 1

Это можно сделать довольно красиво, комбинируя линзы и государственную монаду. Сначала для настройки (я отредактировал ваш, чтобы заставить его скомпилировать с 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

И теперь для нескольких объективов общего назначения, которые позволяют нам заглянуть внутрь 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)

И тогда мы в основном делаем:

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

Здесь %%= просто сообщает нам, какую операцию состояния нужно выполнить, когда мы увеличили масштаб до соответствующего работника с помощью нашего объектива.