简体   繁体   English

Scala State monad - 结合不同的州类型

[英]Scala State monad - combining different state types

I'm wrapping my head around State monad. 我正绕着国家单子行道。 Trivial examples are easy to understand. 琐碎的例子很容易理解。 I'm now moving to a real world case where the domain objects are composite. 我现在转向一个现实世界的案例,其中域对象是复合的。 For example, with the following domain objects (they don't make much sense, just sheer example): 例如,使用以下域对象(它们没有多大意义,只是纯粹的例子):

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

Considering Worker as S types in State[S, +A] monad it's quite easy to write a few combinators like these: State[S, +A] monad中将Worker视为S类型,可以很容易地编写一些这样的组合器:

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.

What is the idiomatic way to combine these with the Master state combinators? 将这些与Master状态组合器结合起来的惯用方法是什么? eg 例如

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

I can implement this like so: 我可以这样实现:

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

What I don't like is that I have to manually run the State monad inside the last transformer. 我不喜欢的是我必须在最后一个变换器内手动运行State monad。 My real world example is a bit more involved. 我的现实世界的例子有点涉及。 With this approach it quickly gets messy. 使用这种方法很快就会变得混乱。

Is there more idiomatic way to run this sort of incremental updates? 是否有更惯用的方式来运行这种增量更新?

It's possible to do this pretty nicely by combining lenses and the state monad. 通过组合镜头和状态monad可以很好地完成这项工作。 First for the setup (I've edited yours lightly to get it to compile with Scalaz 7.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

And now for a couple of general purpose lenses that allow us to look inside a Master : 现在有几个通用镜头让我们可以看到一个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)

And then we're basically done: 然后我们基本完成了:

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

Here the %%= just tells us what state operation to perform once we've zoomed in to the appropriate worker via our lens. 这里的%%=只是告诉我们一旦我们通过镜头放大到合适的工作人员,将执行什么状态操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM