我是scala和功能编程的新手。 我有实现通信协议的用例。 有人能指出以正确的方式处理上下文(存储,创建,执行状态更改)和数据的正确方法。
例如,考虑样本电话协议。 交换机将获得1000次呼叫。因此需要保持每个调用的上下文来正确处理它们。如何以不可变的方式处理上下文处理?
来电者------------------------------被叫[电话开关]
placecall ------------------> -------- Callreceived
警报--------------------------------警报
答案回答--------------------------------
Inconversation
断开--------------------------------断开
如果我正在进行C编程,我将分配一个上下文存储(可能是一个哈希)来存储所有上下文,当我接到一个调用时,我会从商店做一个获取上下文。如果是一个新的调用,我可能会返回null,我需要创建一个新的上下文,如果它是一个现有的调用,我将获得正确的上下文。
如果能够对正确的功能设计模式提供一些见解,那就太棒了。我们会使用IO monad和state monad吗?我们如何为上下文存储建模。当我们进行上下文提取时,我们如何确保功能世界中的并发性。
提前致谢..
问候 马赫什
答案 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解决方案。
结论是,最终你会产生副作用,但你需要知道如何最小化并将它们与程序的其他部分隔离开来。