Search code examples
f#functional-programmingmutableconceptual

mutable state in collection


I'm pretty new to functional programming so this might be a question due to misconception, but I can't get my head around this - from an OOP point of view it seems so obvious...


scenario: Assume you have an actor or micro-service like architecture approach where messages/requests are sent to some components that handle them and reply. Assume now, one of the components stores some of the data from the requests for future requests (e.g. it calculates a value and stores it in a cache so that the next time the same request occurs, no calculation is needed). The data can be hold in memory.

question: How do you in functional programming in general, and especially in f#, handle such a scenario? I guess a static dictionary is not a functional approach and I don't want to include any external things like data stores if possible.

Or more precise: If an application creates data that will be used later in the processing again, where do we store the data?

example: You have an application that executes some sort of tasks on some initial data. First, you store the inital data (e.g. add it to a dictionary), then you execute the first task that does some processing based on a subset of the data, then you execute the second task that adds additional data and so on until all tasks are done...

Now the basic approach (from my understanding) would be to define the data and use the tasks as some sort of processing-chain that forward the processed data, like initial-data -> task-1 -> task-2 -> ... -> done but that does not fit an architecture where getting/adding data is done message-based and asynchronous.

approach:

My initial approach was this

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) 

encapsulated in a module that looks to me like an OOP approach.

After @Gustavo asked me to provide an example and considering his suggestion I've realized that I could do it like this (go one level higher to the place where the functions are actually called):

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
    )

This answers the question for me well since it removed mutability and shared state at all. But when just looking at the first approach, I cannot think of any solution w/o the collection outside the functions


Please note that this question is in f# to explain the environment, the syntax etc. I don't want a solution that works because f# is multi-paradigm, I would like to get a functional approach for that.

I've read all questions that I could find on SO so far but they either prove the theoretical possibility or they use collections for this scenario - if duplicated please point me the right direction.


Solution

  • You can use a technique called memoization which is very common in FP. And it consists precisely on keeping a dictionary with the calculated values.

    Here's a sample implementation:

    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
    

    Note that with memoize you can decorate any function and get a memoized version of it. Here's a sample:

    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
    

    You can see that in the second execution the value was not calculated.