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

时间:2016-09-11 14:18:47

标签: clojure

说我有这样的董事会

(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}

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

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

我知道这可以使用尾递归,但我想避免这样做,因为我几乎可以确定reduce / apply / map的组合会做什么诀窍

2 个答案:

答案 0 :(得分: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)

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

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

首先使用->或"主题"这可以使其更具可读性。宏。它需要一个初始值和缺少第一个参数的表单,然后将代码重写为" thread"每个的返回值作为下一个的第一个参数。

(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以原子的形式传递它。原子可以被认为是一个包含不可变值的可变框。

它使用函数交换!并重置!应用受控突变。函数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}

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

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

;; The value is now a string.
@board ;;=> "And now for something completely different."

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

答案 1 :(得分:1)

我想建议@madstap's fine answer采用不同的方法。

在......

(assoc board :b 4)

... board之类的元素不是函数:它们是函数调用,正如@madstap指出的那样,无法修改(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

请注意compositionsPrivate Sub SendEmail(ByVal workcenter As Integer, ByVal time As Date) Dim objApp As Object Dim objEmail As Object Set objApp = CreateObject("Outlook.Application") Set objEmail = CreateObject("Outlook.MailItem") With objEmail .To = "emailexampe@website.com" .Subject = "Multiple Shop Orders run for line " & workcenter & " at " & time .body = "TEST" .display End With Set objEmail = Nothing Set objApp = Nothing End Sub 是完全通用的 - 它们只是用函数来做事。