处理上下文和状态管理

时间:2016-01-06 03:16:48

标签: scala

我是scala和功能编程的新手。 我有实现通信协议的用例。 有人能指出以正确的方式处理上下文(存储,创建,执行状态更改)和数据的正确方法。

例如,考虑样本电话协议。 交换机将获得1000次呼叫。因此需要保持每个调用的上下文来正确处理它们。如何以不可变的方式处理上下文处理?

来电者------------------------------被叫[电话开关]

placecall ------------------> -------- Callreceived

警报--------------------------------警报

答案回答--------------------------------

Inconversation

断开--------------------------------断开

如果我正在进行C编程,我将分配一个上下文存储(可能是一个哈希)来存储所有上下文,当我接到一个调用时,我会从商店做一个获取上下文。如果是一个新的调用,我可能会返回null,我需要创建一个新的上下文,如果它是一个现有的调用,我将获得正确的上下文。

如果能够对正确的功能设计模式提供一些见解,那就太棒了。我们会使用IO monad和state monad吗?我们如何为上下文存储建模。当我们进行上下文提取时,我们如何确保功能世界中的并发性。

提前致谢..

问候 马赫什

1 个答案:

答案 0 :(得分:3)

任何有用的程序都会使用副作用。在FP中,我们尝试将这些副作用操作推到程序的边缘,同时尽量保持尽可能多的代码。

无论你使用状态monad还是其他技术,最终都会出现副作用。

通常你有一个纯函数,它接受一些状态并将其转换为一个新的状态,并与结果一起返回给调用者。这可以通过常规参数传递或包含在状态monad中的状态来完成,并在其上下文中运行转换。

一个简单的参数传递示例:

object StateExample extends App {

  //--- start of pure part of your program ---//
  type PhoneNumber = String
  type CallState = Map[PhoneNumber, User]

  case class User(username: String)

  def startCall(calls: CallState, caller: User, phone: PhoneNumber): CallState =
    calls + (phone -> caller)

  def finishCall(calls: CallState, phone: PhoneNumber): CallState =
    calls - phone

  def startCallingPeople(calls: CallState) = {
    val intermediateState1 = startCall(calls, User("one"), "123")
    val intermediateState2 = startCall(intermediateState1, User("two"), "456")
    intermediateState2
  }

  def hangupCalls(calls: CallState) = {
    val intermediateState1 = finishCall(calls, "123")
    val intermediateState2 = finishCall(intermediateState1, "456")
    intermediateState2
  }
  // --- end of pure part of the program ---//


  // --- start of impure part of your program ---//
  var callState: CallState = Map()

  def runSimulation(): Unit = {
    println(s"BEFORE ANY CALLS: $callState")
    callState = startCallingPeople(callState)
    println(s"AFTER CALLING 2 PEOPLE: $callState")
    callState = hangupCalls(callState)
    println(s"AFTER HANGING UP: $callState")
  }

  runSimulation()
  // --- end of impure part of your program ---//
}

打印:

BEFORE ANY CALLS: Map()
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two))
AFTER HANGING UP: Map()

了解状态如何从一个函数传递到另一个函数,并且生成转换状态并进一步传递。没有任何副作用,纯粹的'代码的一部分,它只描述了应该如何转换。不纯的'部分代码完成了使用程序其余部分并执行副作用的肮脏工作。

或者你可以在纯粹的'中使用状态monad。程序的一部分,并在“不纯”的程序中运行。部分和保存生成状态类似于状态在上面的var中保存的方式。这是一个例子:

import scalaz.State

object StateMonadExample extends App {

  def startCall(caller: User, phone: PhoneNumber): State[CallState, Unit] =
    State { s => s + (phone -> caller) ->() }

  def finishCall(phone: PhoneNumber): State[CallState, Unit] =
    State { s => (s - phone) ->() }

  def startCallingPeople: State[CallState, Unit] =
    startCall(User("one"), "123").flatMap(_ => startCall(User("two"), "456"))

  def hangupCalls: State[CallState, Unit] =
    finishCall("123").flatMap(_ => finishCall("456"))

  // mutable part of your program
  var callState: CallState = Map()

  // side effecting part of your program
  def runSimulation(): Unit = {
    test1() // intermediate state being saved
    test2() // intermediate state being passed through
    test3() // same as test 2 but also outputs intermediate state without updating it as test 1 does
  }

  def test1(): Unit = {
    // reset initial state just in case
    callState = Map()
    println("TEST 1:")
    println(s"BEFORE ANY CALLS: $callState")
    callState = startCallingPeople.run(callState)._1
    println(s"AFTER CALLING 2 PEOPLE: $callState")
    callState = hangupCalls.run(callState)._1
    println(s"AFTER HANGING UP: $callState")
    println("END OF TEST 1.\n")
  }

  def test2(): Unit = {
    // reset initial state just in case
    callState = Map()
    println("TEST 2:")
    println(s"BEFORE ANY CALLS: $callState")
    val computation = for {
      _ <- startCallingPeople
      _ <- hangupCalls
    } yield ()
    callState = computation.run(callState)._1
    println(s"AFTER CALL AND HANGUP: $callState")
    println("END OF TEST 2.\n")
  }

  def test3(): Unit = {
    // reset initial state just in case
    callState = Map()
    println("TEST 3:")
    println(s"BEFORE ANY CALLS: $callState")
    val computation = for {
      s1 <- startCallingPeople.flatMap(_ => State { s: CallState => println(s"AFTER CALLING 2 PEOPLE: $s"); s -> () })
      _ <- hangupCalls
    } yield ()
    callState = computation.run(callState)._1
    println(s"AFTER HANGING UP: $callState")
    println("END OF TEST 3.\n")
  }

  runSimulation()
}

打印:

TEST 1:
BEFORE ANY CALLS: Map()
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two))
AFTER HANGING UP: Map()
END OF TEST 1.

TEST 2:
BEFORE ANY CALLS: Map()
AFTER CALL AND HANGUP: Map()
END OF TEST 2.

TEST 3:
BEFORE ANY CALLS: Map()
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two))
AFTER HANGING UP: Map()
END OF TEST 3.

请注意State monad负责传递状态。基本上我们只是构建一堆计算,然后通过调用run并传递初始状态来执行它们。

当考虑并发性时,您可以在较小的规模上应用相同的原则,但某些事情开始破裂。例如,如果让2个线程更新相同的状态,则需要对其进行同步,并确保这些线程都不会读取状态的过时版本。同步会导致阻塞并降低程序速度。

实用的方法是将状态保存在某个数据库中(为您管理同步),或以某种方式避免同步。如果我必须将状态保持在内存中,我可能会使用Akka并将每个活动调用表示为演员。 Actor可以安全地封装可变状态,因为它们按顺序处理每个消息。当呼叫结束时,我会杀死演员以释放资源。您可以以不同的方式对应用程序进行分区 - 也许不是每次调用都有一个actor,每个开关可以有一个actor。这实际上取决于要求。请注意,演员拥抱可变性,因此它不是纯粹的FP解决方案。

结论是,最终你会产生副作用,但你需要知道如何最小化并将它们与程序的其他部分隔离开来。

在此处查看完整项目:https://github.com/izmailoff/scala-state-example