セットアップ:
仮想システムでシミュレートされたオブジェクトの状態を表すさまざまなデータ構造のコレクションがいくつかあります。また、これらのオブジェクトを変換する (つまり、元のオブジェクトと 0 個以上のパラメーターに基づいてオブジェクトの新しいコピーを作成する) 関数もいくつかあります。
目標は、ユーザーが (シミュレーションのルール内で) 変換を適用するオブジェクトを選択し、それらの関数をそれらのオブジェクトに適用し、古いオブジェクトを新しいオブジェクトに置き換えることでコレクションを更新できるようにすることです。
小さな変換を大きな変換に組み合わせることで、このタイプの関数を構築できるようにしたいと考えています。次に、この結合関数を評価します。
質問:
これを可能にするためにプログラムを構成するにはどうすればよいですか?
このようなトランザクションを構築するには、どのようなコンビネーターを使用すればよいでしょうか?
アイデア:
- すべてのコレクションを 1 つの巨大な構造に入れ、この構造をあちこちに渡します。
- 基本的に同じことを達成するために状態モナドを使用する
- IORef (または MVar のようなより強力な従兄弟の 1 つ) を使用して、IO アクションを構築します。
- 関数型リアクティブ プログラミング フレームワークを使用する
1 と 2 は、特に最終的にコレクションの一部をデータベースに移動することを想定している場合、多くの荷物を持ち歩いているように見えます。(くそIOモナド)
3 はうまく機能しているように見えますが、OOP を再作成するように見え始めます。また、どのレベルで IORef を使用すればよいかもわかりません。(例:IORef (Collection Obj)
またはCollection (IORef Obj)
またはdata Obj {field::IORef(Type)}
)
4 はスタイルが最も機能的であると感じますが、表現力の点であまり見返りがなく、多くのコードの複雑さを生み出しているようにも見えます.
例
私はウェブストアフロントを持っています。私は、(とりわけ)在庫数と価格を含む製品のコレクションを維持しています。また、ストアの信用を持つユーザーのコレクションもあります。
ユーザーがやって来て、購入する 3 つの製品を選択し、ストア クレジットを使用してチェックアウトします。3 つの製品の在庫を減らした新しい製品コレクションを作成し、ユーザー アカウントが引き落とされた新しいユーザー コレクションを作成する必要があります。
これは、次のことを意味します。
checkout :: Cart -> ProductsCol -> UserCol -> (ProductsCol, UserCol)
しかし、その後、生活はより複雑になり、税金に対処する必要があります。
checkout :: Cart -> ProductsCol -> UserCol -> TaxCol
-> (ProductsCol, UserCol, TaxCol)
そして、注文を配送キューに確実に追加する必要があります。
checkout :: Cart
-> ProductsCol
-> UserCol
-> TaxCol
-> ShipList
-> (ProductsCol, UserCol, TaxCol, ShipList)
などなど…
私が書きたいのは次のようなものです
checkout = updateStockAmount <*> applyUserCredit <*> payTaxes <*> shipProducts
applyUserCredit = debitUser <*> creditBalanceSheet
しかし、タイプチェッカーは私に夢中になったでしょう。checkout
またはapplyUserCredit
関数がモジュール化され、抽象化されたままになるように、このストアをどのように構造化すればよいでしょうか? この問題を抱えているのは私だけではありませんよね?