изменяемое состояние в коллекции

Я новичок в функциональном программировании, поэтому это может быть вопрос из-за неправильного понимания, но я не могу понять это - с точки зрения ООП это кажется таким очевидным...


сценарий: предположим, что у вас есть действующий или микросервисный архитектурный подход, в котором сообщения/запросы отправляются некоторым компонентам, которые их обрабатывают и отвечают. Теперь предположим, что один из компонентов сохраняет некоторые данные из запросов для будущих запросов (например, он вычисляет значение и сохраняет его в кэше, чтобы при следующем поступлении того же запроса вычисления не требовались). Данные могут храниться в памяти.

вопрос: как вы в функциональном программировании вообще, и особенно в 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) 

инкапсулированный в модуль, который выглядит как ООП-подход.

После того, как @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 до сих пор, но они либо доказывают теоретическую возможность, либо используют коллекции для этого сценария - если они повторяются, пожалуйста, укажите мне правильно направление.


person MrWombat    schedule 03.10.2017    source источник
comment
Наличие статической и неизменной карты/словаря для уже вычисленного значения также подходит для функционального программирования. Вы просто комбинируете/цепляете функции 1. GetFromCache 2. Если кэш не существует, вычислите. Тип Option идеально подходит для возврата из функций GetFromCache.   -  person Fabio    schedule 03.10.2017
comment
Вы также можете прочитать о том, что такое закрытие.   -  person Jared Smith    schedule 03.10.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

Вы можете видеть, что при втором выполнении значение не вычислялось.

person Gus    schedule 03.10.2017
comment
Спасибо за ваш ответ! Кажется, я понял, но это не совсем вписывается в описываемую мной распределенную архитектуру, верно? Я добавил пример к моему первоначальному вопросу, чтобы быть более точным в моем сценарии. - person MrWombat; 03.10.2017
comment
@MrWombat Думаю, это подходит. Почему бы нет? Можете ли вы добавить небольшой пример кода? - person Gus; 03.10.2017
comment
@MrWombat Читая ваше описание, мне кажется, что функция начальных данных - это та, которую нужно запомнить. - person Gus; 03.10.2017
comment
Большое спасибо, я думаю, что теперь у меня есть ваша идея, и это помогло мне найти решение (возможно, вы можете проверить, соответствует ли это вашему предложению). - person MrWombat; 03.10.2017