Scala状态单子-组合不同的状态类型

Scala状态单子-组合不同的状态类型,scala,functional-programming,state,monads,state-monad,Scala,Functional Programming,State,Monads,State Monad,我正绕着州单子转。琐碎的例子很容易理解。我现在转到一个真实世界的例子,其中域对象是复合的。例如,对于以下域对象(它们没有多大意义,只是纯粹的示例): 考虑到Worker作为S类型处于状态[S,+A]monad,编写如下几个组合符非常容易: type WorkerState[+A] = State[Worker, A] def update(message: Message): WorkerState[Unit] = State.modify { w => w.copy(elapse

我正绕着州单子转。琐碎的例子很容易理解。我现在转到一个真实世界的例子,其中域对象是复合的。例如,对于以下域对象(它们没有多大意义,只是纯粹的示例):

考虑到
Worker
作为
S
类型处于
状态[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.
我可以这样实现:

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))
        }
    }
我不喜欢的是,我必须在最后一个转换器中手动运行状态monad。我的真实世界的例子有点复杂。这种方法很快就会变得一团糟


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

通过将镜头和状态单子结合起来,可以很好地做到这一点。首先是设置(我对您的进行了少量编辑,以便使用Scalaz 7.1进行编译):

然后我们基本上完成了:

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

这里的
%%=
告诉我们,一旦我们通过镜头放大到合适的工作者,应该执行什么状态操作。

问得好!你是指一些具体的
State
实现,比如
scalaz
?它看起来绝对像
LensT
用法的好例子,迫不及待地想看看一些专家的答案。我正在运行我自己的可怜人monad实现。我是否正确地理解,为了实现这一点,我需要一个透镜的实现以及状态与透镜的集成(特别是%%=操作)?另外,您对使用Scalaz与Monad的手写实现有何看法?如果您正在做这种事情,我强烈建议您使用Scalaz或CAT,而不是使用自己的。@ak正如我所看到的,您自己的
State
Scalaz.State
之间的主要区别在于
运行状态
的结果元组中的顺序。我假设您至少可以通过隐式转换来粘合这些实现。这似乎是一个解决常见问题的好方法。如果有多个不同类型的组件以及它们自己的状态,它也可以很好地工作。我想知道这种状态+镜头的组合是否已经被任何类型库所采用?也许还会有一个typeclass来帮助将调用转发到组件。
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))
        }
    }
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
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)