2015-06-02 30 views
13

我正在围绕国家单元包围我的头。简单的例子很容易理解。我现在正转向一个真实世界的情况,即域对象是复合的。例如,下面的域对象(他们没有多大意义,只是纯粹的例子):斯卡拉州立单元 - 结合不同的州立类型

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

考虑WorkerS类型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. 

将这些与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)) 
     } 
    } 

我不喜欢的是,我必须手动运行的最后一个变压器内部的状态单子。我的真实世界的例子有一点涉及。采用这种方法很快就会变得混乱。

是否有更习惯的方式来运行这种增量更新?

+0

不错的问题!你是否指的是像'scalaz'这样的具体'State'实现? – Odomontois

+0

这绝对是'LensT'用法的好例子,迫不及待地想要看一些专家的回答。 – Odomontois

回答

8

通过组合镜头和状态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 

现在一对夫妇的通用镜头,让我们看一个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) 

这里%%=只是告诉我们要执行什么样的状态运行,一旦我们已经放大到适当借机通过我们的镜头。

+0

我正在运行我自己的穷人的monad实现。我是否明白地理解,为了实现这个工作,我需要一个Lens的实现和State与Lens的集成(特别是%% = operation)? –

+0

你还有什么使用Scalaz与monads的手写实现? –

+0

如果你正在做这种事情,我强烈建议使用Scalaz或猫来代替自己滚动。 –