【问题标题】:Scala State monad - combining different state typesScala State monad - 结合不同的状态类型
【发布时间】:2015-06-02 04:24:52
【问题描述】:

我正在围绕着 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)

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

我不喜欢的是我必须在最后一个转换器中手动运行 State monad。我的现实世界的例子涉及更多。使用这种方法,它很快就会变得混乱。

有没有更惯用的方式来运行这种增量更新?

【问题讨论】:

  • 好问题!你指的是一些具体的State 实现,比如scalaz
  • 这绝对是LensT 用法的好例子,迫不及待地想看看专家的回答。

标签: scala functional-programming state monads state-monad


【解决方案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)

这里的%%= 只是告诉我们一旦我们通过镜头放大到适当的工作人员后要执行什么状态操作。

【讨论】:

  • 我正在运行我自己的穷人的 monad 实现。我是否正确理解要使这项工作正常进行,我需要实现 Lens 并将 State 与 Lens 集成(特别是 %%= 操作)?
  • 另外,您对使用 Scalaz 与 monad 的手写实现有何看法?
  • 如果您正在做这种事情,我强烈建议您使用 Scalaz 或猫,而不是自己滚动。
  • @ak 如我所见,您自己的Statescalaz.State 之间的主要区别在于runState 的结果元组中的顺序。我假设您至少可以通过隐式转换来粘合这些实现。
  • 这似乎是解决常见问题的好方法。它也可以很好地工作,您有多个不同类型的组件,它们有自己的状态。我想知道 State + Lens 的这种组合是否已被任何类型级库采用?也许还会有一个类型类来帮助将调用转发到组件。
猜你喜欢
  • 2016-03-31
  • 2019-05-19
  • 1970-01-01
  • 2011-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多