集合中的可变状态
mutable state in collection
我是函数式编程的新手,所以这可能是一个由于误解导致的问题,但我无法理解这个问题 - 从 OOP 的角度来看,它似乎很明显...
场景:
假设您有一个参与者或类似微服务的体系结构方法,其中 messages/requests 被发送到一些处理它们并回复的组件。现在假设,其中一个组件存储来自请求的一些数据以供将来请求(例如,它计算一个值并将其存储在缓存中,以便下次发生相同请求时,不需要计算)。
数据可以保存在内存中。
问题:
在一般的函数式编程中,尤其是在 f# 中,您如何处理这种情况?我想静态字典不是一种功能性方法,如果可能的话我不想包括任何外部的东西,比如数据存储。
或更准确地说:
如果一个应用程序创建数据,稍后将在处理中再次使用,我们在哪里存储数据?
示例:您有一个应用程序在一些初始数据[=57=上执行某种任务 ].首先,您存储初始数据(例如,将其添加到字典中),然后执行第一个任务,该任务根据数据的子集进行一些处理,然后执行第二个任务 adds 其他数据等等,直到完成所有任务...
现在的基本方法(根据我的理解)是定义数据并将任务用作某种处理链来转发已处理的数据,例如 initial-data -> task-1 -> task-2 -> ... -> done
但这不适合 getting/adding 数据基于消息和异步完成的体系结构。
方法:
我最初的方法是这样的
type Record = { }
let private dummyStore = new System.Collections.Concurrent.ConcurrentBag<Record>()
let search comparison =
let matchingRecords = dummyStore |> Seq.where (comparison)
if matchingRecords |> Seq.isEmpty
then EmptyFailedRequest
else Record (matchingRecords |> Seq.head)
let initialize initialData =
initialData |> Seq.iter (dummyStore.Add)
let add newRecord =
dummyStore.Add(newRecord)
封装在我看来像 OOP 方法的模块中。
在@Gustavo 要求我提供一个示例并考虑他的建议后,我意识到我可以这样做(向上一级到实际调用函数的地方):
let handleMessage message store =
// all the operations from above but now with Seq<Record> -> ... -> Seq<Record>
store
let agent = MailboxProcessor.Start(fun inbox->
let rec messageLoop store = async{
let! msg = inbox.Receive()
let modifiedStore = handleMessage msg store
return! messageLoop modifiedStore
}
messageLoop Seq.empty
)
这很好地回答了我的问题,因为它完全消除了可变性和共享状态。但是当只看第一种方法时,我想不出任何解决方案 w/o 函数外的集合
请注意,这个问题在 f# 中用于解释环境、语法等。我不想要一个有效的解决方案,因为 f# 是多范式,我想为此获得一个功能性方法。
到目前为止,我已经阅读了我能在 SO 上找到的所有问题,但它们要么证明了理论上的可能性,要么使用了集合对于这种情况 - 如果重复请指出正确的方向。
您可以使用一种称为 memoization 的技术,这在 FP 中很常见。
它恰好包括保存一个包含计算值的字典。
这是一个示例实现:
open System
open System.Collections.Concurrent
let getOrAdd (a:ConcurrentDictionary<'A,'B>) (b:_->_) k = a.GetOrAdd(k, b)
let memoize f =
let dic = new ConcurrentDictionary<_,_>()
getOrAdd dic f
请注意,使用 memoize
您可以修饰任何函数并获得它的记忆版本。这是一个示例:
let f x =
printfn "calculating f (%i)" x
2 * x
let g = memoize f // g is the memoized version of f
// test
> g 5 ;;
calculating f (5)
val it : int = 10
> g 5 ;;
val it : int = 10
可以看到第二次执行时没有计算出值。
我是函数式编程的新手,所以这可能是一个由于误解导致的问题,但我无法理解这个问题 - 从 OOP 的角度来看,它似乎很明显...
场景: 假设您有一个参与者或类似微服务的体系结构方法,其中 messages/requests 被发送到一些处理它们并回复的组件。现在假设,其中一个组件存储来自请求的一些数据以供将来请求(例如,它计算一个值并将其存储在缓存中,以便下次发生相同请求时,不需要计算)。 数据可以保存在内存中。
问题: 在一般的函数式编程中,尤其是在 f# 中,您如何处理这种情况?我想静态字典不是一种功能性方法,如果可能的话我不想包括任何外部的东西,比如数据存储。
或更准确地说: 如果一个应用程序创建数据,稍后将在处理中再次使用,我们在哪里存储数据?
示例:您有一个应用程序在一些初始数据[=57=上执行某种任务 ].首先,您存储初始数据(例如,将其添加到字典中),然后执行第一个任务,该任务根据数据的子集进行一些处理,然后执行第二个任务 adds 其他数据等等,直到完成所有任务...
现在的基本方法(根据我的理解)是定义数据并将任务用作某种处理链来转发已处理的数据,例如 initial-data -> task-1 -> task-2 -> ... -> done
但这不适合 getting/adding 数据基于消息和异步完成的体系结构。
方法:
我最初的方法是这样的
type Record = { }
let private dummyStore = new System.Collections.Concurrent.ConcurrentBag<Record>()
let search comparison =
let matchingRecords = dummyStore |> Seq.where (comparison)
if matchingRecords |> Seq.isEmpty
then EmptyFailedRequest
else Record (matchingRecords |> Seq.head)
let initialize initialData =
initialData |> Seq.iter (dummyStore.Add)
let add newRecord =
dummyStore.Add(newRecord)
封装在我看来像 OOP 方法的模块中。
在@Gustavo 要求我提供一个示例并考虑他的建议后,我意识到我可以这样做(向上一级到实际调用函数的地方):
let handleMessage message store =
// all the operations from above but now with Seq<Record> -> ... -> Seq<Record>
store
let agent = MailboxProcessor.Start(fun inbox->
let rec messageLoop store = async{
let! msg = inbox.Receive()
let modifiedStore = handleMessage msg store
return! messageLoop modifiedStore
}
messageLoop Seq.empty
)
这很好地回答了我的问题,因为它完全消除了可变性和共享状态。但是当只看第一种方法时,我想不出任何解决方案 w/o 函数外的集合
请注意,这个问题在 f# 中用于解释环境、语法等。我不想要一个有效的解决方案,因为 f# 是多范式,我想为此获得一个功能性方法。
到目前为止,我已经阅读了我能在 SO 上找到的所有问题,但它们要么证明了理论上的可能性,要么使用了集合对于这种情况 - 如果重复请指出正确的方向。
您可以使用一种称为 memoization 的技术,这在 FP 中很常见。 它恰好包括保存一个包含计算值的字典。
这是一个示例实现:
open System
open System.Collections.Concurrent
let getOrAdd (a:ConcurrentDictionary<'A,'B>) (b:_->_) k = a.GetOrAdd(k, b)
let memoize f =
let dic = new ConcurrentDictionary<_,_>()
getOrAdd dic f
请注意,使用 memoize
您可以修饰任何函数并获得它的记忆版本。这是一个示例:
let f x =
printfn "calculating f (%i)" x
2 * x
let g = memoize f // g is the memoized version of f
// test
> g 5 ;;
calculating f (5)
val it : int = 10
> g 5 ;;
val it : int = 10
可以看到第二次执行时没有计算出值。