繁体   English   中英

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

[英]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})

请注意, composecompositions完全是通用的-它们只是对函数起作用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM