2011-12-16 16 views
4

セットアップ:私は、様々なデータ構造魔女のいくつかのコレクションを有する更新要素

は、仮想システムにシミュレートされたオブジェクトの状態を表します。私はまた、これらのオブジェクトを元の0個以上のパラメータに基づいて変換する多数の関数を持っています。

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

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

質問

どのように私はこれを可能にする私のプログラムを構築するには?

このようなトランザクションを構築するためにどのようなコンビネータを使用しますか?

アイデア

  1. 1つの巨大な構造にすべてのコレクションを入れて、この構造を周りに渡します。

官能性反応プログラミングフレームワークを使用し

  • を基本的に同じこと
  • 使用IORef(またはMVARのようなそのより強力ないとこの1)を達成するために、状態モナドを使用してIOアクションを構築1と2は、とりわけ私が最終的にコレクションの一部をデータベースに移動させることを想定すると、周囲に多くの荷物を運ぶように見えます。 (Darn IO Monad)

    3はうまくいくように見えますが、OOPを再現するように見えるようになります。私はまた、IORefをどのレベルで使うべきかも分かりません。 (例えばIORef (Collection Obj)またはCollection (IORef Obj)またはdata Obj {field::IORef(Type)}

    4は、スタイルの中で最も機能を感じているが、また、表現力の面で多くのペイオフせずにコードの複雑さの多くを作成しているようです。


    私は、Webストアフロントを持っています。私は在庫の数量と価格を(他のものの中でも)商品のコレクションを維持しています。また、店舗にクレジットを持っているユーザーの集まりもあります。

    ユーザーは、andsが購入する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の機能がモジュール化され抽象化された状態でこのストアを構成するにはどうすればよいですか?私はこの問題を抱える唯一の人にはなりませんよね?

  • +1

    ちょうど明白である:あなたは様々な特定のタイプAに対して、 'A - > A 'に似ているタイプの様々な機能を持っています。それらを複合アクションに結合して、何らかの種類の共有状態を更新できるようにする必要があります。共有された状態にあるすべての機能をすべての機能に認識させたくないのですか? –

    +0

    C.A.McCann:正解。いくつかのものは、それらをカリングすることによって 'B - > A - > A'のようなものから派生する必要があるかもしれません。 –

    答えて

    6

    これを解消しましょう。

    AA)の部分的なアプリケーションから派生した可能性があり、以前の値に関して新しいタイプの値を指定するタイプの場合、A -> Aのようなタイプの関数を使用します。このようなタイプの各タイプは、その関数の機能に固有のものでなければならず、プログラムの開発に応じてこれらのタイプを変更するのは簡単です。

    共有状態には、上記の更新機能で使用されているすべての情報が含まれているものと思われます。さらに、直接的に作用する機能以外のものに大きな影響を与えることなく、状態の内容を変更することが可能でなければならない。

    また、上記を妥協することなく、更新機能を抽象的に組み合わせたいと考えています。

    我々は、簡単な設計のいくつかの必要な機能推測することができる:中間層は、状態の部分ができるように、完全な共有状態と各機能が必要とする仕様との間に、必要であろう

    • を残りの部分とは独立して置き換えられる。

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

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

    このブレークダウンによって、レイヤ全体が大幅にモジュール化されます。特に、タイプクラスは、関連するインスタンスをスワップインできるように、必要な機能を記述するために定義することができます。

    特に、これは本質的にあなたのアイデア2と3を統一します。

    • 、レコードタイプの共有状態を作るStateモナドに格納し、界面層を提供するために、レンズを使用します。提案型クラスのインタフェースは、以下のような複数のアプローチが、可能になります

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

    • 共有状態を、TChanのコレクションにしてください。外部データストアと非同期で通信するために、それぞれのスレッドを個別に読み書きするようにしてください。

    または他のさまざまな変形が可能です。

    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)] 
    
    関連する問題