2

我有一个看起来像这样的地图列表:

(def balances ({:name "Steve" :money 1000} {:name "Bill" :money 1000} ...))

我正在尝试编写一个函数,将给定数量(比如说 100)的史蒂夫钱转移给比尔并更新数据结构:

({:name "Steve" :money 900} {:name "Bill" :money 1100})

我认为该函数每次都应该期望余额作为参数,并且看起来像这样:

(defn transfer [current-balances amount sender receiver] ... )

像这样的函数看起来如何?这是一种管理和更新账户余额的聪明方法吗?一般来说,我的程序将采用相当长的转移列表并将它们迭代地应用于余额结构。由于Clojure中的持久数据结构,我是否总是必须将平衡结构传递给传递函数?

4

3 回答 3

3

事实上,您的余额都包含在一个单一的伞形数据结构下,并且该结构是一个适当的值,您的转移函数可以简单地接受描述帐户当前状态的结构,另一个描述变化的结构,并产生新的状态的帐户。这使您也可以将转移操作视为适当的值,这应该有助于处理非常长的转移列表:) 我唯一的改变是使用余额映射而不是列表

bar> (def balances {"Steve" {:money 1000} "Bill" {:money 1000}})
#'bar/balances

bar> (def transfers [["Steve" "Bill" 100] ["Bill" "Steve" 100] 
                     ["Steve" "Bill" 10 ] ["Bill" "Steve" 10 ] 
                     ["Bill" "Steve" 10 ]])
#'bar/transfers

然后定义一个简单的转移函数,它采用其中一个并将其应用于帐户

(defn transfer [balances [from to ammount]] 
  (-> balances 
      (update-in [from :money] - ammount) 
      (update-in [to   :money] + ammount)))

该函数可用于直接将任意转账序列缩减为所有账户的状态:

bar> (reduce transfer balances transfers)
{"Bill" {:money 990}, "Steve" {:money 1010}}

接受来自客户的新转账的函数然后可以使用此函数来更改您选择将银行存储在其中的任何内容(数据库、原子、代理等)的状态

bar> (def bank (agent {:current balances :ledger []}))
#'bar/bank

bar> (defn accept-transfers [transfers] 
       (send bank assoc :current (reduce transfer (:current @bank) transfers) 
                        :ledger (concat transfers (:ledger @bank))))
#'bar/accept-transfers

bar> (accept-transfers transfers)
#<Agent@2eb9bc1: {:current {"Bill" {:money 1000}, "Steve" {:money 1000}}, :ledger []}>

这将转账放在银行队列中(并返回 REPL 在转账可能正在运行时快速打印的代理,当我们查看银行时,我们看到所有这些转账都已应用。

bar> bank
#<Agent@2eb9bc1: {:current {"Bill" {:money 990}, "Steve" {:money 1010}}, 
                  :ledger (["Steve" "Bill" 100] ["Bill" "Steve" 100] 
                           ["Steve" "Bill" 10] ["Bill" "Steve" 10] 
                           ["Bill" "Steve" 10])}>
于 2012-12-31T18:48:25.833 回答
1

Clojure 数据是不可变的,您无法修改它。您需要使用 STM。

这是示例代码(简化为问题点)。

(def account1 {:name "Foo" :money (ref 1000)})
(def account2 {:name "Bar" :money (ref 1000)})

(defn transfer
  [from to amount]
  (dosync
     (alter (:money from) - amount)
     (alter (:money to) + amount)))

(transfer account1 account2 100)

(println @(:money account1))
(println @(:money account2))

更多信息请访问http://clojure.org/refshttp://clojure.org/atoms,也许还有http://clojure.org/agents

于 2012-12-31T13:46:54.980 回答
0

这是我的两分钱。我希望它也会有所帮助。

(def balances {1 (ref {:name "Steve"
                       :money 1000})
               2 (ref {:name "Bill"
                       :money 1000})
               3 (ref {:name "John"
                       :money 1000})})

(defn balance [person-id]
  ((deref (balances person-id)) :money))

(defn credit [balance amount]
  (- balance amount))

(defn debet [balance amount]
  (+ balance amount))

(defn update-account [person-id operation-fn amount]
  (alter (get balances person-id)
         #(assoc % :money
                 (operation-fn (:money %) amount))))

(defn credit-account [person-id amount]
  (update-account person-id credit amount))

(defn debet-account [person-id amount]
  (update-account person-id debet amount))

(defn transfer [sender-id receiver-id amount]
  (if (< (credit (balance sender-id) amount) 0)
    {:result :insufficient-fund}
    (do (credit-account sender-id amount)
        (debet-account receiver-id amount)
        {:result :ok})))

测试

(defn transaction []
  (dosync (transfer 1 2 100)
          (transfer 2 3 200)
          (transfer 3 1 200)))

(transaction)
-> {:result :ok}

balances
-> {1 #<Ref@b54dba: {:money 1100, :name "Steve"}>,
    2 #<Ref@1020230: {:money 900, :name "Bill"}>,
    3 #<Ref@1803641: {:money 1000, :name "John"}>}
于 2012-12-31T21:24:46.520 回答