Updating items from multiple collections with dynamic features

Customization

I have several collections of various data structures that represent the state of simulated objects in a virtual system. I also have a number of functions that convert (that is, create a new copy of an object based on the original and 0 or more parameters) these objects.

The goal is to allow the user to select an object to apply transformations (as part of the modeling rules), apply those functions to these objects, and update collections by replacing old objects with new ones.

I would like to be able to create a function of this type by combining smaller conversions into larger ones. Then evaluate this combined function.

Questions

How do I customize my program to make this possible?

What combinator do I use to create such a transaction?

Ideas

  • Put all the collections in one huge structure and pass this structure around.
  • Use the state monad to do basically the same thing.
  • Use an IORef (or one of its more powerful cousins ​​like MVar) and create an IO action
  • Using a functional reactive software platform

1 and 2, it looks like they carry a lot of luggage, especially if I assume that in the end some collections will be moved to the database. (Darn Io Monad)

3 seems to work well, but it starts to look a lot like recreating OOP. I'm also not sure at what level to use IORef. (for example, IORef (Collection Obj) or Collection (IORef Obj) or data Obj {field::IORef(Type)} )

4 feels most functional in style, but it also seems to create a lot of code complexity without significant impact in terms of expressiveness.


Example

I have a web store. I keep a collection of products with (among other things) stock quantity and price. I also have a collection of users who have credit in the store.

The user comes and selects 3 products for purchase and goes for registration using a loan in the store. I need to create a new collection of products that has stock in stock for 3 products, create a new collection of users with a debit user account.

This means that I get the following:

 checkout :: Cart -> ProductsCol -> UserCol -> (ProductsCol, UserCol) 

But then life gets complicated, and I need to deal with taxes:

 checkout :: Cart -> ProductsCol -> UserCol -> TaxCol -> (ProductsCol, UserCol, TaxCol) 

And then I must definitely add the order to the delivery queue:

 checkout :: Cart -> ProductsCol -> UserCol -> TaxCol -> ShipList -> (ProductsCol, UserCol, TaxCol, ShipList) 

And so on...

What I would like to write is something like

 checkout = updateStockAmount <*> applyUserCredit <*> payTaxes <*> shipProducts applyUserCredit = debitUser <*> creditBalanceSheet 

but the type-controller would be apoplectic on me. How to structure this storage so that the checkout or applyUserCredit remain modular and abstract? I can't be the only one to have this problem, right?

+4
source share
2 answers

Ok, let it break.

You have β€œupdate” functions with types such as A -> A for various types of A that can be obtained from a partial application that defines a new value of some type in terms of the previous value. Each such type A must be specific to what this function does, and it should be easy to change these types as the program develops.

You also have some kind of general condition that supposedly contains all the information used by any of the above update functions. In addition, it should be possible to change what the state contains without significantly affecting anything other than the functions that act directly on it.

In addition, you want to be able to abstractly combine update functions without sacrificing the above.

We can derive several necessary functions of a simple construction:

  • An intermediate level is needed between the full general state and the specifications necessary for each function, allowing you to project and change parts of the state independently of the others.

  • The types of update functions themselves are, by definition, incompatible with the real general structure, so to build them you first need to combine everything with a part of the intermediate layer. This will give you updates that apply to all state, which can then be compiled in an obvious way.

  • The only operations that are necessary for the general condition as a whole are interaction with the intermediate layer and all that may be required to maintain the changes made.

This partition allows the overall volume of each layer to be modular; in particular, type classes can be defined to describe the necessary functionality to replace any corresponding instance.

In particular, this, in essence, combines your ideas 2 and 3. There is an inherent monadic context here, and the proposed class class interface allows many approaches, such as:

  • Make the general state a record type, save it in the State monad, and use lenses to maintain the level of the interface.

  • Make a record type for the general state containing something like STRef for each part, and combine the field selectors with the ST monad update actions to provide an interface level.

  • Create the TChan s shared storage, with separate streams, to read / write them, if necessary, for asynchronous communication with an external data storage.

Or any number of other options.

+6
source

You can save your state in the recording and use lenses to update the state. This allows you to write individual state update components as simple, focused functions that can be combined to create more complex checkout functions.

 {-# LANGUAGE TemplateHaskell #-} import Data.Lens.Template import Data.Lens.Common import Data.List (foldl') import Data.Map ((!), Map, adjust, fromList) type User = String type Item = String type Money = Int -- money in pennies type Prices = Map Item Money type Cart = (User, [(Item,Int)]) type ProductsCol = Map Item Int type UserCol = Map User Money data StoreState = Store { _stock :: ProductsCol , _users :: UserCol , msrp :: Prices } deriving Show makeLens ''StoreState updateProducts :: Cart -> ProductsCol -> ProductsCol updateProducts (_,c) = flip (foldl' destock) c where destock p' (item,count) = adjust (subtract count) item p' updateUsers :: Cart -> Prices -> UserCol -> UserCol updateUsers (name,c) p = adjust (subtract (sum prices)) name where prices = map (\(itemName, itemCount) -> (p ! itemName) * itemCount) c checkout :: Cart -> StoreState -> StoreState checkout cs = (users ^%= updateUsers c (msrp s)) . (stock ^%= updateProducts c) $ s test = checkout cart store where cart = ("Bob", [("Apples", 2), ("Bananas", 6)]) store = Store initialStock initialUsers prices initialStock = fromList [("Apples", 20), ("Bananas", 10), ("Lambdas", 1000)] initialUsers = fromList [("Bob", 20000), ("Mary", 40000)] prices = fromList [("Apples", 100), ("Bananas", 50), ("Lambdas", 0)] 
+3
source

Source: https://habr.com/ru/post/1386774/


All Articles