Clojure映射哈希映射的函数列表,每次更新哈希映射

Jac*_*ans 1 clojure

说我有这样的董事会

(def board {:a 10 :b 12})
Run Code Online (Sandbox Code Playgroud)

以及这样的功能列表

(def funcs [(assoc board :a 2)
            (assoc board :b 4)
            (assoc board :a (inc (get board :a)))])
Run Code Online (Sandbox Code Playgroud)

我如何将列表中的每个操作应用到我的电路板上,每次都以功能方式更新.

funcs在我的repl中进行评估,在调用每个函数后给出一个返回值列表,但每次都会保持电路板本身不变

user=> funcs
[{:a 2, :b 12} {:a 10, :b 4} {:a 11, :b 12}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望每次运行函数时更新电路板的值

所以在运行all命令后,板的最终状态应为: {:a 3 :b 4}

我知道这可能是使用尾递归但我想避免这样做,因为我几乎可以肯定reduce/ apply/ 的组合map会做的伎俩

mad*_*tap 9

clojure的一个定义特征是它的数据结构是不可变的.这意味着board永远不会改变,对数据结构进行操作的函数返回数据结构的修改副本.所以你所做的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)
Run Code Online (Sandbox Code Playgroud)

如果你想要一个应用了这些功能的更新板,你需要通过函数传递返回值,因为原始板永远不会改变,它们都只返回其输入的更新副本.

(def updated-board
  ;; The innermost forms are evaluated first.
  (update (assoc (assoc board :a 2) :b 4) :a inc))
Run Code Online (Sandbox Code Playgroud)

通过使用->或"线程优先"宏可以使这更具可读性.它采用初始值和缺少第一个参数的表单,然后重写代码以"线程化"每个参数的返回值作为下一个参数的第一个参数.

(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)
Run Code Online (Sandbox Code Playgroud)

这是"在clojure中思考"的方法,函数通常只接受不可变值并返回其他不可变值.

但有时你需要一些可变的东西,而clojure以原子的形式提供它.原子可以被认为是包含不可变值的可变框.

它使用函数交换!并重置!应用受控突变.并且函数deref获取当前值.

(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}
Run Code Online (Sandbox Code Playgroud)

reset!将board的值设置为另一个值,例如=在"普通"语言中.这样做通常不是惯用的,因为它有点对读者说,原子的新价值与旧的价值无关,但是clojure是务实的,有时它就是你所需要的.

(reset! board "And now for something completely different.")

;; The value is now a string.
@board ;;=> "And now for something completely different."
Run Code Online (Sandbox Code Playgroud)

作为旁白.数据结构实际上并不是彼此的深层副本,幕后有魔力使其几乎与更新数据结构一样有效,但从程序员的角度来看,它们相当于其他语言的深层副本.