4

セットアップ:

仮想システムでシミュレートされたオブジェクトの状態を表すさまざまなデータ構造のコレクションがいくつかあります。また、これらのオブジェクトを変換する (つまり、元のオブジェクトと 0 個以上のパラメーターに基づいてオブジェクトの新しいコピーを作成する) 関数もいくつかあります。

目標は、ユーザーが (シミュレーションのルール内で) 変換を適用するオブジェクトを選択し、それらの関数をそれらのオブジェクトに適用し、古いオブジェクトを新しいオブジェクトに置き換えることでコレクションを更新できるようにすることです。

小さな変換を大きな変換に組み合わせることで、このタイプの関数を構築できるようにしたいと考えています。次に、この結合関数を評価します。

質問:

これを可能にするためにプログラムを構成するにはどうすればよいですか?

このようなトランザクションを構築するには、どのようなコンビネーターを使用すればよいでしょうか?

アイデア:

  1. すべてのコレクションを 1 つの巨大な構造に入れ、この構造をあちこちに渡します。
  2. 基本的に同じことを達成するために状態モナドを使用する
  3. IORef (または MVar のようなより強力な従兄弟の 1 つ) を使用して、IO アクションを構築します。
  4. 関数型リアクティブ プログラミング フレームワークを使用する

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関数がモジュール化され、抽象化されたままになるように、このストアをどのように構造化すればよいでしょうか? この問題を抱えているのは私だけではありませんよね?

4

2 に答える 2

6

さて、これを分解しましょう。

前の値に関して何らかのタイプの新しい値を指定する、部分的なアプリケーションから派生する可能性のあるA -> Aさまざまな特定のタイプのようなタイプの「更新」関数があります。Aそのような型Aはそれぞれ、その関数が何をするかに固有のものである必要があり、プログラムの開発に応じてそれらの型を簡単に変更できる必要があります。

また、おそらく前述の更新機能のいずれかで使用されるすべての情報を含む、ある種の共有状態もあります。さらに、状態に直接作用する機能以外に大きな影響を与えることなく、状態に含まれるものを変更できる必要があります。

さらに、上記を損なうことなく、更新機能を抽象的に組み合わせることができるようにする必要があります。

単純な設計に必要ないくつかの機能を推測できます。

  • 完全に共有された状態と各関数に必要な詳細との間に中間層が必要になり、状態の一部を投影して残りとは独立して置き換えることができます。

  • 更新関数自体の型は、定義上、実際の共有構造と互換性がないため、それらを構成するには、最初にそれぞれを中間層部分と結合する必要があります。これにより、状態全体に作用する更新が得られ、明らかな方法で構成できます。

  • 共有状態全体で必要な唯一の操作は、中間層とのインターフェースと、行われた変更を維持するために必要な操作だけです。

この内訳により、各レイヤー全体を大幅にモジュール化できます。特に、型クラスを定義して必要な機能を記述し、関連するインスタンスをスワップインできるようにすることができます。

特に、これは本質的にあなたのアイデア 2 と 3 を統合します。ここにはある種の固有のモナド コンテキストがあり、提案された型クラス インターフェイスは次のような複数のアプローチを可能にします。

  • 共有状態をレコード型にし、それをStateモナドに格納し、レンズを使用してインターフェース層を提供します。

  • 共有状態を for each ピースのようなものを含むレコード タイプSTRefにし、フィールド セレクターをSTモナド更新アクションと組み合わせてインターフェイス レイヤーを提供します。

  • 共有状態を のコレクションにTChanし、必要に応じて個別のスレッドで読み取り/書き込みを行い、外部データ ストアと非同期に通信します。

または任意の数の他のバリエーション。

于 2011-12-16T22:56:12.343 に答える
3

状態をレコードに保存し、レンズを使用して状態の一部を更新できます。これにより、個々の状態更新コンポーネントを、より複雑なcheckout関数を構築するために構成できる単純で焦点を絞った関数として記述できます。

{-# 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 c s = (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)]
于 2011-12-16T22:38:43.850 に答える