Clojure不变性实践
我试图理解Clojure的不变性最佳实践,我有一个简单的例子,我不断地重新声明(更新)“新订单”,但我不确定这是否正确Clojure不变性实践,clojure,Clojure,我试图理解Clojure的不变性最佳实践,我有一个简单的例子,我不断地重新声明(更新)“新订单”,但我不确定这是否正确 (defrecord Order [fplate splate]) (def new-orders clojure.lang.PersistentQueue/EMPTY) (defn add-order [orders order] (conj orders order)) (defn cook [order] ()) (defn cook-order [order
(defrecord Order [fplate splate])
(def new-orders clojure.lang.PersistentQueue/EMPTY)
(defn add-order [orders order]
(conj orders order))
(defn cook [order] ())
(defn cook-order [orders]
(cook (first orders)) (pop orders))
;;order1
(def o1 (->Order "Soup" "Fish&Chips"))
(def new-orders (add-order new-orders o1))
;;order2
(def o2 (->Order "Salad" "Hamburger"))
(def new-orders (add-order new-orders o2))
;;order3
(def o3 (->Order "Rice" "Steak"))
(def new-orders (add-order new-orders o3))
;;cook order
(def new-orders (cook-order new-orders))
(peek new-orders)
谢谢,
这绝对不是用Clojure或任何其他FP语言进行函数式编程的正确方法。尽管您使用了Clojure作为实现语言,但您的代码本质上仍然是必需的 正确的方法是,每当需要某个数据结构的新版本时,使用修改过的内容创建新的数据结构。比如:
(let [a []
b (conj a "order1")
c (conj b "order2")]
(println c))
在本例中,conj通过向结构中添加一个新元素(作为conj的参数)来创建一个新的数据结构。旧结构没有任何修改,这意味着它是不可变的
如果你真的需要某种状态,Clojure有一些原语,比如atom。但是,首先尝试在没有可修改的中心状态的情况下编写函数代码。这绝对不是用Clojure或任何其他FP语言进行函数编程的正确方法。尽管您使用了Clojure作为实现语言,但您的代码本质上仍然是必需的 正确的方法是,每当需要某个数据结构的新版本时,使用修改过的内容创建新的数据结构。比如:
(let [a []
b (conj a "order1")
c (conj b "order2")]
(println c))
在本例中,conj通过向结构中添加一个新元素(作为conj的参数)来创建一个新的数据结构。旧结构没有任何修改,这意味着它是不可变的
如果你真的需要某种状态,Clojure有一些原语,比如atom。但是首先尝试编写没有可修改的中心状态的函数代码。下面是一种更典型的方法。它使用
spyx pretty
函数,但如果需要,您可以替换println
:
(ns tst.demo.core
(:require [tupelo.core :as t] ))
(defrecord Order [fplate splate])
(def orders-queue (atom []))
(defn add-order [order]
(swap! orders-queue conj order))
(defn cook [order] (println "cooking: " (pr-str order)))
(add-order (->Order "Soup" "Fish&Chips")) ; order1
(t/spyx-pretty orders-queue)
(add-order (->Order "Salad" "Hamburger")) ; order2
(t/spyx-pretty orders-queue)
(add-order (->Order "Rice" "Steak")) ; order3
(t/spyx-pretty orders-queue)
; cook orders
(newline)
(doseq [order @orders-queue]
(cook order))
结果如下:
orders-queue =>
#<Atom@4147f771: [{:fplate "Soup", :splate "Fish&Chips"}]>
orders-queue =>
#<Atom@4147f771:
[{:fplate "Soup", :splate "Fish&Chips"}
{:fplate "Salad", :splate "Hamburger"}]>
orders-queue =>
#<Atom@4147f771:
[{:fplate "Soup", :splate "Fish&Chips"}
{:fplate "Salad", :splate "Hamburger"}
{:fplate "Rice", :splate "Steak"}]>
cooking: #tst.demo.core.Order{:fplate "Soup", :splate "Fish&Chips"}
cooking: #tst.demo.core.Order{:fplate "Salad", :splate "Hamburger"}
cooking: #tst.demo.core.Order{:fplate "Rice", :splate "Steak"}
订单队列=>
#
订单队列=>
#
订单队列=>
#
烹饪:#tst.demo.core.Order{:fplate“汤”,splate“鱼和薯条”}
烹饪:#tst.demo.core.Order{:fplate“沙拉”,splate“汉堡”}
烹饪:#tst.demo.core.Order{:fplate“Rice”,splate“Steak”}
下面是一种更典型的方法来完成此示例。它使用
spyx pretty
函数,但如果需要,您可以替换println
:
(ns tst.demo.core
(:require [tupelo.core :as t] ))
(defrecord Order [fplate splate])
(def orders-queue (atom []))
(defn add-order [order]
(swap! orders-queue conj order))
(defn cook [order] (println "cooking: " (pr-str order)))
(add-order (->Order "Soup" "Fish&Chips")) ; order1
(t/spyx-pretty orders-queue)
(add-order (->Order "Salad" "Hamburger")) ; order2
(t/spyx-pretty orders-queue)
(add-order (->Order "Rice" "Steak")) ; order3
(t/spyx-pretty orders-queue)
; cook orders
(newline)
(doseq [order @orders-queue]
(cook order))
结果如下:
orders-queue =>
#<Atom@4147f771: [{:fplate "Soup", :splate "Fish&Chips"}]>
orders-queue =>
#<Atom@4147f771:
[{:fplate "Soup", :splate "Fish&Chips"}
{:fplate "Salad", :splate "Hamburger"}]>
orders-queue =>
#<Atom@4147f771:
[{:fplate "Soup", :splate "Fish&Chips"}
{:fplate "Salad", :splate "Hamburger"}
{:fplate "Rice", :splate "Steak"}]>
cooking: #tst.demo.core.Order{:fplate "Soup", :splate "Fish&Chips"}
cooking: #tst.demo.core.Order{:fplate "Salad", :splate "Hamburger"}
cooking: #tst.demo.core.Order{:fplate "Rice", :splate "Steak"}
订单队列=>
#
订单队列=>
#
订单队列=>
#
烹饪:#tst.demo.core.Order{:fplate“汤”,splate“鱼和薯条”}
烹饪:#tst.demo.core.Order{:fplate“沙拉”,splate“汉堡”}
烹饪:#tst.demo.core.Order{:fplate“Rice”,splate“Steak”}
也许您可以使用atom?看看这个相关的问题:也许你可以使用atom?看到这个相关的问题:嗨,洛科里,谢谢你的回答。我明白你的意思。在真实场景中(餐厅),订单不断地来来去去,必须有一个点来保持“实际订单”,因此“准备”或“更新”等其他功能必须依赖。在let中发生的事情会留在let中,所以我想这是我仍然缺少的东西。谢谢你的回答。我明白你的意思。在真实场景中(餐厅),订单不断地来来去去,必须有一个点来保持“实际订单”,因此“准备”或“更新”等其他功能必须依赖。在let中发生的事情会留在let中,所以我想这是我仍然缺少的东西。谢天谢地,这无疑是对问题中代码的改进,尽管我更愿意看到关于如何在功能上真正做到这一点的解释,因为问题是关于不变性实践的。有一件事我不明白:在
ns
子句中导入的demo.core
名称空间是什么?(我不介意这里的tupelo
,因为您使用:as
来引用它,并解释如何使用clojure.core)我通常使用测试ns
进行演示,但忘了删除未使用的:use
内容。但是,这个答案没有任何单元测试,可能在主名称空间中。如果我错了,请纠正我,但从您的示例和注释中,我了解如果需要维护和更新任何类型集合的状态,我可以使用atom和swap!。问题是关于不变性的,因为通过更新某些集合,我正在对其进行变异,在本例中,必须维护订单的动态列表。谢谢。是的,原子和交换!是Clojure为您提供的可能性之一。交换!您并不是直接修改状态,而是以原子方式替换状态的新值。您可以通过对前一状态执行某些操作(如conj操作或其他操作),从前一状态计算新状态。原子和交换!从某种意义上说,它是原始的,如果您需要这样的东西,它不会为同步事务更新提供任何保护。这当然是对问题中代码的改进,尽管我更希望看到关于如何在功能上真正做到这一点的解释,因为问题询问的是不变性实践。有一件事我不明白:在ns
子句中导入的demo.core
名称空间是什么?(我不介意这里的tupelo
,因为您使用:as
来引用它,并解释如何使用clojure.core)我通常使用测试ns
进行演示,但忘了删除未使用的:use
内容。但是,这个答案没有任何单元测试,可能在主名称空间中。如果我错了,请纠正我,但从您的示例和注释中,我了解如果需要维护和更新任何类型集合的状态,我可以使用atom和swap!。问题是关于不变性的,因为通过更新某些集合,我正在对其进行变异,在本例中,必须维护订单的动态列表。谢谢,是的,atom an