用变化的状态转换序列的功能/Clojure 方式是什么?

Gis*_*shu 1 clojure

问题背景与股票交易有关。当进行销售时,我正在尝试更新特定股票的持有量。简化摘录

;; @holdings - an atom
{ "STOCK1" {:trades [Trade#{:id 100 :qty 50}, Trade#{ :id 140 :qty 50}]}
 "STOCK2" ... }
Run Code Online (Sandbox Code Playgroud)

现在考虑到 的销售交易Trade{:id 200 :stock "STOCK1", :qty 75},我希望所持股份能够反映

{ "STOCK1" {:trades [Trade#{:id 100 :qty 0}, Trade#{ :id 140 :qty 25}]} }
;; or better drop the records with zero qty.
{ "STOCK1" {:trades [Trade#{ :id 140 :qty 25}]} }
Run Code Online (Sandbox Code Playgroud)

功能性的答案让我望而却步..我能看到的只是一个doseq带有原子的循环来保持状态(比如销售数量可能会被 1 次或 n 次交易所满足)——但感觉就像 Clojure 中的 C。

对此是否有更符合 clojure 的解决方案?地图看起来不太合适,因为每个记录处理都需要更新外部状态(待售数量 75 -> 25 -> 0)

免责声明:Clojure 新手,想学习。

ako*_*ond 5

(require '[com.rpl.specter :as s])


(let [stocks     {"STOCK1" {:trades [{:trade/id 100 :trade/qty 50}, {:trade/id 140 :trade/qty 50}]}}
      sale-trade {:trade/id 200 :trade/stock "STOCK1" :trade/qty 75}
      trade-path [(s/keypath (:trade/stock sale-trade) :trades) s/ALL]
      qty-path   (conj trade-path :trade/qty)
      [new-qty _] (reduce (fn [[new-amounts leftover] v]
                              (let [due-amount (min v leftover)]
                                  [(conj new-amounts (- v due-amount)) (- leftover due-amount)]))
                          [[] (:trade/qty sale-trade)]
                          (s/select qty-path stocks))]
    (->> stocks
         (s/setval (s/subselect qty-path) new-qty)
         (s/setval [trade-path #(zero? (:trade/qty %))] s/NONE)))

=> {"STOCK1" {:trades [#:trade{:id 140, :qty 25}]}}
Run Code Online (Sandbox Code Playgroud)