【问题标题】:Updating elements of multiple collections with dynamic functions使用动态函数更新多个集合的元素
【发布时间】:2012-01-22 08:06:21
【问题描述】:

设置

我有几个不同数据结构的集合,它们代表虚拟系统中模拟对象的状态。我还有一些函数可以转换这些对象(即基于原始和 0 个或更多参数创建对象的新副本)。

目标是允许用户选择一些对象来应用转换(在模拟规则内),将这些函数应用于这些对象,并通过用新对象替换旧对象来更新集合。

我希望能够通过将较小的转换组合成较大的转换来构建这种类型的函数。然后评估这个组合函数。

问题

如何构建我的程序以实现这一点?

我使用什么样的组合器来构建这样的交易?

想法

  1. 将所有收藏品放在一个巨大的结构中,然后传递这个结构。
  2. 使用状态单子来完成基本相同的事情
  3. 使用 IORef(或其更强大的表亲之一,如 MVar)并构建 IO 操作
  4. 使用功能响应式编程框架

1 和 2 似乎携带了很多包袱,特别是如果我设想最终将一些集合移动到数据库中。 (该死的 IO Monad)

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

但是类型检查器会让我中风。如何构建这个商店,使checkoutapplyUserCredit 函数保持模块化和抽象?我不可能是唯一一个有这个问题的人,对吧?

【问题讨论】:

  • 只是要明确一点:您有各种类型类似于A -&gt; A 的函数,用于各种特定类型A;您希望能够将它们组合成复合动作,更新某种共享状态;并且您不希望每个函数都知道处于共享状态的其他所有内容?
  • C.A.McCann:正确;虽然有些可能需要通过柯里化从 B -&gt; A -&gt; A 之类的东西派生出来。

标签: haskell data-modeling ioref


【解决方案1】:

好的,让我们分解一下。

对于各种特定类型A,您有“更新”函数,其类型为A -&gt; A,它可以从部分应用程序派生,根据以前的值指定某种类型的新值。每个此类A 都应该特定于该函数的功能,并且随着程序的开发应该很容易更改这些类型。

您还拥有某种共享状态,它可能包含上述任何更新函数使用的所有信息。此外,应该可以更改状态包含的内容,而不会显着影响除直接作用于它的函数之外的任何内容。

此外,您希望能够抽象地组合更新函数,而不影响上述内容。

我们可以推断出简单设计的一些必要特征:

  • 在完全共享状态和每个函数所需的细节之间需要一个中间层,允许独立于其余部分投射和替换状态片段。

  • 根据定义,更新函数本身的类型与没有真正的共享结构不兼容,因此要组合它们,您需要首先将每个与中间层部分结合起来。这将为您提供作用于整个状态的更新,然后可以以明显的方式进行组合。

  • 整个共享状态所需的唯一操作是与中间层接口,以及维护所做更改可能需要的任何操作。

这种分解使得每一整层都可以在很大程度上模块化;特别是,可以定义类型类来描述必要的功能,允许交换任何相关实例。

特别是,这基本上统一了您的想法 2 和 3。这里有某种固有的单子上下文,建议的类型类接口将允许多种方法,例如:

  • 将共享状态设为记录类型,将其存储在State monad 中,并使用镜头提供接口层。

  • 将共享状态设为记录类型,其中每个片段包含类似STRef 的内容,并将字段选择器与ST monad 更新操作结合起来以提供接口层。

  • 使共享状态成为TChans 的集合,并使用单独的线程来读取/写入它们,以便与外部数据存储进行异步通信。

或任何数量的其他变体。

【讨论】:

    【解决方案2】:

    您可以将您的状态存储在记录中,并使用镜头更新状态片段。这让您可以将各个状态更新组件编写为简单、专注的函数,这些函数可以组合成更复杂的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)]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-23
      • 1970-01-01
      • 1970-01-01
      • 2013-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多