在Clojure函数中重新绑定var的具体缺点是什么?

时间:2012-08-23 20:18:33

标签: clojure

在探索Clojure中计算阶乘的各种方法时,我提出了以下(非惯用)函数:

(defn factorial-using-do-dotimes [x]
  (do
    (def a 1)
    (dotimes [i x]
      (def a (* a (inc i)))))
  a)

REPL:

user=> (factorial-using-do-dotimes 5)
120

这有什么具体的缺点(除了“非惯用”)?性能?正确性(即可能的缺陷)?

3 个答案:

答案 0 :(得分:10)

如果您尝试并行运行此代码,则竞争条件可以为 导致它默默地产生错误的答案

首先自己运行它:

core> (factorial-using-do-dotimes 10)
3628800                                                                                                    

然后运行两个副本,但让快速的一个提前完成:

core> (do (future (do (java.lang.Thread/sleep 5) 
                      (factorial-using-do-dotimes  1200))) 
          (factorial-using-do-dotimes 10))
3628800                                                                                                    

然后将它们拉近距离:

core> (do (future (do (java.lang.Thread/sleep 1) 
                      (factorial-using-do-dotimes 1200))) 
          (factorial-using-do-dotimes 10))
3628800    

然后同时运行它们:

core> (do (future (do (java.lang.Thread/sleep 0) 
                      (factorial-using-do-dotimes 1200))) 
          (factorial-using-do-dotimes 10))
54698277723986154311681531904000000N                                                                       

这不是正在运行的任何一个因子的答案 (factorial-using-do-dotimes 1200)长3176位

答案出错了。

注意:我更改了factorial-using-do-dotimes函数以使用*'而不是*,因此我可以在更大的示例上运行它,以便在示例中更容易命中时间。

答案 1 :(得分:3)

在函数体内部使用def不是惯用的,但不是明确的未定义行为,并且考虑到实现变量的方式,使用(dotimes .. (def ..))可能比使用(loop ... (recur ...))慢得多,特别是当你使用基本类型,如数字和类型提示时。

不对vars进行这种“动态”修改的主要原因是它没有任何理由使代码复杂化。循环/复发和瞬态的一些更惯用的组合应该在正常情况下使你获得性能,因为你可以摆脱clojure。它仍然是线程安全的,可预测的和可读的。

编辑:作为一个具体的缺陷:当同时从多个线程调用时,您的示例代码无法正常工作。

答案 2 :(得分:0)

重新绑定def可能会在需要同步的环境中产生不可预测的结果。如果需要修改状态,请考虑使用refs,atoms或agents。

您可以在for循环中累积值,如此

(defn chk-key-present-in-sos
  ""
  [mapped-sos cmp-key cmp-seq-vals]
  (if (seq-of-maps? mapped-sos)
    (for [mapped-row mapped-sos
        :let [matched-rows mapped-row]
        :when (chk-one-val-present mapped-row cmp-key cmp-seq-vals)]
      matched-rows)
    nil))

您可以使用reduce来产生最终结果。

(defn key-pres?
"This function accepts a value (cmp-val) and a vector of vectors
(parsed output from clojure-csv) and returns the match value
back if found and nil if not found.

Using reduce, the function searches every vector row to see
if cmp-val is at the col-idx location in the vector."

[cmp-val cmp-idx csv-data]
   (reduce
       (fn [ret-rc csv-row]
          (if (= cmp-val (nth csv-row col-idx nil))
            (conj ret-rc cmp-val)))
       []
       csv-data))

您可以使用into累积。

最后,您始终可以使用递归并让终止步骤返回 累积的斐波纳契值。