管理可变状态的最佳方法是什么?

时间:2012-12-10 15:23:52

标签: scala

我刚刚在Coursera完成了Martin Odersky的scala课程。 Scala是我的第一个FP语言,我对限制可变状态的想法很兴奋。这允许更容易的并发性并且还使代码超级可维护。

在学习这一切的同时,我意识到你可以保证对象的可变性,只要它没有可变变量并且只引用了不可变对象。所以现在我可以通过创建一个新状态而不是修改旧状态来做任何事情,尽可能使用尾递归。

大。所以我到目前为止只能做到这一点。在某些时候,我的应用程序需要能够修改一些现有的状态。我知道在这一点上放入并发控制的地方,锁,等等等等。我仍然默认使用我一直使用的标准多线程并发控制。

哦scala社区,还有更好的方法吗? Monads可能吗?

编辑:这个问题有点笼统,所以我想给一个用例: 我有一个机器学习算法,可存储多个数据集合。它们具有返回数据(训练等)的更新表示的函数,所有函数都是不可变的。最终,我可以将这种返回更新状态模式保持在运行模拟的实际对象上。它具有可变的状态并保存对集合的引用。我可能想要分发到多核或多系统。

3 个答案:

答案 0 :(得分:4)

这是一个主观问题,所以我不会试图回答“哪个是最好的”部分。如果您的主要关注点是多线程并发的上下文,那么一个选项可能是Software Transactional Memory

Implementation提供了STM的Akka (参见快速入门)。根据您的使用情况,它可能是重量级或过度杀伤,但再次,它可能比一堆锁更好。与锁不同,STM倾向于乐观,与数据库事务的方式相同。与数据库事务一样,您可以在事务上下文中显式更改共享状态,并且如果检测到冲突,您将以原子方式提交或重新尝试更改。基本上你必须将你的所有状态包装在 Refs 中,它只能在'原子'块中操作 - 实现为一个方法,它采用一个闭包来操作你的Ref和ScalaSTM确保您所在州的整个操作集成功或失败 - 不会发生中途或不一致的更改。

这利用了Scala的隐式参数 - 对Ref的所有操作都需要一个事务对象作为参数,这由给予{的闭包接收{1}}并且可以声明为隐式,因此atomic中的所有代码都可以以非常自然而安全的方式编写。

为了使这个有用,您需要使用提供的事务数据结构;这意味着使用atomic代替TSetSet代替TMap。当在事务上下文中使用时(在原子块内),它们提供全有或全无的更新语义。这非常像 clojure的持久集合。您还可以在Map之外构建自己的事务数据结构,以便在这些Ref块中使用。

如果你不反对括号,refs的clojure解释非常好:http://clojure.org/refs

答案 1 :(得分:2)

根据您的使用案例,您可能会坚持使用部分复制的深层不可变对象结构,而不是实际改变它们(类似于与其原始列表共享后缀的“已更新”不可变列表)。所谓的镜头是处理此类结构的好方法,请在this SO questionthis blog post中阅读。

当您不希望全局可观察到更改时,仅使用不可变结构。不可变结构很可能不是一个选项的示例是在共享列表上工作的两个并发客户端,其中客户端A完成的修改必须由客户端B可观察,反之亦然。

答案 2 :(得分:0)

我建议最好的方法是将可变变量存储在Akka actor中,使用传入和传出Akka actor的消息来发送和接收这个可变引用。使用不可变数据结构。

我有一个如下的StorageActor。每次通过StoreEntity存储某些内容时,变量entityMap都会更新。此外,它不需要是易变的,仍然有效。

Akka演员是事物可以改变的地方,消息传入和传递到纯粹的功能世界。

import akka.actor.Actor
import java.util.UUID
import com.orsa.minutesheet.entity.Entity

case class EntityRef(entity: Option[Entity])

case class FindEntity(uuid: UUID)
case class StoreEntity[T >: Entity](uuid: UUID, entity: Option[T])

class StorageActor extends Actor {

  private var entityMap = Map[UUID, Entity]()

  private def findEntityByUUID(uuid:UUID): Option[Entity] = entityMap.get(uuid)

  def receive = {
    case FindEntity(uuid) => sender ! EntityRef( findEntityByUUID(uuid) )
    case StoreEntity(uuid, entity) =>
      entity match {
        case Some(store) => entityMap += uuid -> store.asInstanceOf[Entity]
        case None => entityMap -= uuid
      }
  }
}