说我有这样的董事会
(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
的组合会做什么诀窍
答案 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
请注意compositions
和Private 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
是完全通用的 - 它们只是用函数来做事。