集合中的可变状态

mutable state in collection

提问人:MrWombat 提问时间:10/3/2017 最后编辑:MrWombat 更新时间:10/3/2017 访问量:171

问:

我对函数式编程很陌生,所以这可能是一个由于误解而产生的问题,但我无法解决这个问题 - 从 OOP 的角度来看,这似乎很明显......


场景:假设你有一个类似 actor 或微服务的架构方法,其中消息/请求被发送到一些处理它们并回复的组件。现在假设,其中一个组件存储了来自未来请求请求的一些数据(例如,它计算一个值并将其存储在缓存中,以便下次发生相同的请求时,不需要计算)。 数据可以保存在内存中。

问题:在一般的函数式编程中,尤其是在 f# 中,您如何处理这种情况?我想静态字典不是一种函数式方法,如果可能的话,我不想包含任何外部的东西,如数据存储。

或者更准确地说:如果应用程序创建的数据将在以后的处理中再次使用,我们将数据存储在哪里?

例:您有一个应用程序,它对某些初始数据执行某种任务。首先,你存储初始数据(例如,将其添加到字典中),然后你执行第一个任务,根据数据的子集进行一些处理,然后你执行第二个任务,添加额外的数据,依此类推,直到所有任务都完成......

现在的基本方法(根据我的理解)是定义数据并将任务用作转发已处理数据的某种处理链,例如但不适合基于消息和异步完成获取/添加数据的架构。initial-data -> task-1 -> task-2 -> ... -> done

方法:

我最初的做法是这样的

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
    )

这很好地回答了我的问题,因为它完全消除了可变性和共享状态。但是,当只看第一种方法时,我想不出任何没有函数之外的集合的解决方案


请注意,这个问题是用 f# 格式来解释环境、语法等。我不想要一个有效的解决方案,因为 f# 是多范式的,我想为此获得一种功能方法。

到目前为止,我已经阅读了我在 SO 上能找到的所有问题,但它们要么证明了理论上的可能性,要么在这种情况下使用集合 - 如果重复,请指出正确的方向。

f# 函数式编程 可变 概念

评论

1赞 Fabio 10/3/2017
对于已经计算出的值,使用静态和不可变的映射/字典对于函数式编程也是可以的。您只需组合/链接到函数 1。GetFromCache 2.如果缓存不存在,则进行计算。选项类型是从 GetFromCache 函数返回的完美候选者
0赞 Jared Smith 10/3/2017
您可能还想阅读什么是闭包。

答:

4赞 Gus 10/3/2017 #1

您可以使用一种称为记忆的技术,这在 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

您可以看到,在第二次执行中,未计算该值。

评论

0赞 MrWombat 10/3/2017
谢谢你的回答!我想我明白了,但它并不适合我所描述的“分布式架构”,对吧?我在最初的问题中添加了一个示例,以便更准确地说明我的场景
0赞 Gus 10/3/2017
@MrWombat我认为它很合适。为什么不呢?你能添加一个小的示例代码吗?
0赞 Gus 10/3/2017
@MrWombat 阅读您的描述,在我看来,initial-data 函数是要记住的函数。
1赞 MrWombat 10/3/2017
非常感谢你,我想我现在已经有了你的想法,它帮助我找到了一个解决方案(也许你可以检查一下这是否符合你的建议)。