ak.*_*ak. 15 monads state functional-programming scala state-monad
我正绕着国家单子行道.琐碎的例子很容易理解.我现在转向一个现实世界的案例,其中域对象是复合的.例如,使用以下域对象(它们没有多大意义,只是纯粹的例子):
case class Master(workers: Map[String, Worker])
case class Worker(elapsed: Long, result: Vector[String])
case class Message(workerId: String, work: String, elapsed: Long)
Run Code Online (Sandbox Code Playgroud)
考虑到Worker作为S类型的State[S, +A]单子它很容易写几个组合子这样的:
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.
Run Code Online (Sandbox Code Playgroud)
将这些与Master状态组合器结合起来的惯用方法是什么?例如
type MasterState[+A] = State[Master, A]
def updateAndGetElapsedTime(message: Message): MasterState[Option[Long]]
Run Code Online (Sandbox Code Playgroud)
我可以这样实现:
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))
}
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢的是我必须在最后一个变换器内手动运行State monad.我的现实世界的例子有点涉及.使用这种方法很快就会变得混乱.
是否有更惯用的方式来运行这种增量更新?
通过组合镜头和状态monad可以很好地完成这项工作.首先进行设置(我已轻轻编辑你的内容以使用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
Run Code Online (Sandbox Code Playgroud)
现在,对于一些通用镜头,我们可以看到内部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)
Run Code Online (Sandbox Code Playgroud)
然后我们基本完成了:
def updateAndGetElapsedTime(message: Message): State[Master, Option[Long]] =
workerLens(message.workerId) %%= updateAndGetElapsed(message)
Run Code Online (Sandbox Code Playgroud)
这里%%=只是告诉我们一旦我们通过镜头放大到合适的工作人员,将执行什么状态操作.