[英]Clojure map list of functions over hash-map, updating the hash-map each time
说我有一个这样的董事会
(def board {:a 10 :b 12})
还有这样的功能列表
(def funcs [(assoc board :a 2)
(assoc board :b 4)
(assoc board :a (inc (get board :a)))])
我将如何将列表中的每个操作应用到我的板上,使其每次都以功能性方式进行更新。
在我的repl中评估funcs
,在调用每个函数后给出返回值的列表,但是每次时板子本身都保持不变
user=> funcs
[{:a 2, :b 12} {:a 10, :b 4} {:a 11, :b 12}
理想情况下,我希望每次运行功能时都要更新板的价值
因此,在运行所有命令后,电路板的最终状态应为: {:a 3 :b 4}
我知道使用尾递归是可行的,但我想避免这样做,因为我几乎可以肯定, reduce
/ apply
/ map
的组合可以reduce
clojure的定义特征之一是其数据结构是不可变的。 这意味着该board
将永远不会改变,对数据结构进行操作的功能会返回该数据结构的修改后的副本。 因此,您在funcs
所做的就是将三个不同的funcs
(一个应用了功能的原始面板)向量化。
通常,您想要的是将一堆函数应用于某个初始值,其中每个函数先获取函数的返回值,然后将返回值用于某些东西。 通常在函数参数中传递它。
;; First of all, there's a function for that
(assoc board :a (inc (get board :a)))
;; The function update takes a map and a key and a function
;; It applies the function to value currently at key,
;; then sets key in the retuned "copy" of the map to be the return value of the function.
;; Equivalent to the above
(update board :a inc)
如果要应用这些功能的更新板,则需要通过这些函数传递返回值,因为原始板永远都不会改变,它们都只返回其输入的更新副本。
(def updated-board
;; The innermost forms are evaluated first.
(update (assoc (assoc board :a 2) :b 4) :a inc))
通过使用->
或“线程优先”宏,可以使其更具可读性。 它采用一个初始值和缺少第一个参数的形式,然后重写代码以“线程化”每个参数的返回值作为下一个参数的第一个参数。
(def updated-board-threaded
(-> board
(assoc :a 2)
(assoc :b 4)
(update :a inc)))
;; You can expand the macro to see for yourself
;; that the two examples are exactly equivalent.
(macroexpand-1 '(-> board
(assoc :a 2)
(assoc :b 4)
(update :a inc)))
;;=> (update (assoc (assoc board :a 2) :b 4) :a inc)
这是“思考clojure”的方法,函数通常只获取不可变的值并返回其他不可变的值。
但是有时您需要一些易变的东西,而Clojure会以原子的形式提供这种东西。 可以将原子视为包含不可变值的可变框。
它使用功能交换! 并重置! 应用受控突变。 然后该函数解引用以获取当前值。
(def board (atom {:a 10, :b 12}))
;; I'll define a function that takes a board and returns an updated version of it.
(defn do-stuff-with-board [b]
(-> b
(assoc :a 2)
(assoc :b 4)
(update :a inc)))
;; Get the current value of board.
(deref board) ;;=> {:a 10, :b 12}
;; Swap takes an atom and a function and
;; sets the value of the atom to be the return value of the function
(swap! board do-stuff-with-board)
;; Now the mutable board atom contains a new immutable value.
(deref board) ;;=> {:a 3, :b 4}
;; derefing an atom is a very usual operation, so there's syntax sugar for it
;; Equivalent to (deref board)
@board ;;=> {:a 3, :b 4}
reset!
将board的值设置为另一个值,例如“正常”语言中的=。 这样做通常不是惯用的,因为它对读者说原子的新值与旧原子无关,但是clojure是务实的,有时正是您所需要的。
(reset! board "And now for something completely different.")
;; The value is now a string.
@board ;;=> "And now for something completely different."
作为旁白。 数据结构实际上并不是彼此的深层副本,幕后的魔力使其几乎可以与就地更新数据结构一样高效,但是从程序员的角度来看,它们等效于其他语言中的深层副本。
我想提出一种不同的方法来解决@madstap的问题 。
在...
(def funcs [(assoc board :a 2)
(assoc board :b 4)
(assoc board :a (inc (get board :a)))])
... (assoc board :b 4)
元素不是函数:它们是函数调用 ,正如@madstap指出的那样,它们无法修改任何board
所指的内容。
我们可以毫不费力地将它们转变为适当的功能:
(def funcs [(fn [board] (assoc board :a 2))
(fn [board] (assoc board :b 4))
(fn [board] (assoc board :a (inc (get board :a))))])
这里的board
是当地人。 任何不同的标识符也可以:
(def funcs [(fn [b] (assoc b :a 2))
(fn [b] (assoc b :b 4))
(fn [b] (assoc b :a (inc (get b :a))))])
我们可以编写一个函数来组成它们:
(defn compose [fs]
(fn [x] (reduce (fn [a f] (f a)) x fs)))
这是标准comp
的简化版本。 它首先应用功能到最后,而不是最后应用。 现在,例如,如果
(def board {:a 10 :b 12})
... 然后
((compose funcs) board)
;{:a 3, :b 4}
此外,我们可以修改compose
以显示结果链:
(defn compositions [fs]
(fn [x] (reductions (fn [a f] (f a)) x fs)))
((compositions funcs) board)
;({:a 10, :b 12} {:a 2, :b 12} {:a 2, :b 4} {:a 3, :b 4})
请注意, compose
和compositions
完全是通用的-它们只是对函数起作用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.