为什么ClojureScript原子不实现完整协议?

8
swap!函数是Clojure中最常用的工具之一,它执行instance?检查。在编程中,我们通常要避免围绕类型检查实现条件语句,而要优先使用多态(协议)。但很奇怪的是,ClojureScript没有直接将ISwap协议实现于原子(atom)上,而是在公共swap! api中做了是否为原子(atom)的检查后才回退到该协议。
我认为这种策略可能出于性能原因,因为原子(atom)是swap!和其他许多原子方法的主要用例。这样做对吗?
我更喜欢将原子(atom)的api实现作为实际协议的一部分,这样就不必进行这种检查了。
(defn swap!
  "Atomically swaps the value of atom to be:
  (apply f current-value-of-atom args). Note that f may be called
  multiple times, and thus should be free of side effects.  Returns
  the value that was swapped in."
  ([a f]
     (if (instance? Atom a)
       (reset! a (f (.-state a)))
       (-swap! a f)))
  ([a f x]
     (if (instance? Atom a)
       (reset! a (f (.-state a) x))
       (-swap! a f x)))
  ([a f x y]
     (if (instance? Atom a)
       (reset! a (f (.-state a) x y))
       (-swap! a f x y)))
  ([a f x y & more]
     (if (instance? Atom a)
       (reset! a (apply f (.-state a) x y more))
       (-swap! a f x y more))))
1个回答

5
看起来与性能相关:http://dev.clojure.org/jira/browse/CLJS-760

添加一个IAtom协议,其中包含一个-reset!方法和一个针对cljs.core/reset!的快速路径。

在这里看jsperf - http://jsperf.com/iatom-adv

最新版本的Chrome和Firefox受到约20-30%的减速影响。早期版本的Firefox减速高达60-70%。

进一步下文中提到,决定将IAtom拆分为两个协议:IReset和ISwap。但是这是David采用的实现方式,它进行了类型检查,我想这样做是为了提高速度。

不幸的是,不清楚为什么没有让Atom实现IReset(以及ISwap),也不清楚为什么没有寻找那些东西。原始补丁的工作原理也不清楚。它基本上将reset!的实现放在了一个instance检查下,并添加了-reset!的路径:

diff --git a/src/cljs/cljs/core.cljs b/src/cljs/cljs/core.cljs
index 9fed929..c6e41ab 100644
--- a/src/cljs/cljs/core.cljs
+++ b/src/cljs/cljs/core.cljs
@@ -7039,6 +7039,9 @@ reduces them without incurring seq initialization"

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;;

+(defprotocol IAtom
+  (-reset! [o new-value]))
+
 (deftype Atom [state meta validator watches]
   IEquiv
   (-equiv [o other] (identical? o other))
@@ -7088,14 +7091,16 @@ reduces them without incurring seq initialization"
   "Sets the value of atom to newval without regard for the
   current value. Returns newval."
   [a new-value]
+  (if (instance? Atom a)
     (let [validate (.-validator a)]
       (when-not (nil? validate)
-      (assert (validate new-value) "Validator rejected reference state")))
+        (assert (validate new-value) "Validator rejected reference state"))
       (let [old-value (.-state a)]
         (set! (.-state a) new-value)
         (when-not (nil? (.-watches a))
-      (-notify-watches a old-value new-value)))
-  new-value)
+          (-notify-watches a old-value new-value))
+        new-value))
+    (-reset! a new-value)))

 (defn swap!
   "Atomically swaps the value of atom to be:

这是在33692b79a114faf4bedc6d9ab38d25ce6ea4b295提交的(或者至少非常接近)。然后其他协议更改是在3e6564a72dc5e175fc65c9767364d05af34e4968中完成的:

commit 3e6564a72dc5e175fc65c9767364d05af34e4968
Author: David Nolen <david.nolen@gmail.com>
Date:   Mon Feb 17 14:46:10 2014 -0500

    CLJS-760: break apart IAtom protocol into IReset & ISwap

diff --git a/src/cljs/cljs/core.cljs b/src/cljs/cljs/core.cljs
index 25858084..e4df4420 100644
--- a/src/cljs/cljs/core.cljs
+++ b/src/cljs/cljs/core.cljs
@@ -7061,9 +7061,12 @@ reduces them without incurring seq initialization"

 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;;

-(defprotocol IAtom
+(defprotocol IReset
   (-reset! [o new-value]))

+(defprotocol ISwap
+  (-swap! [o f] [o f a] [o f a b] [o f a b xs]))
+
 (deftype Atom [state meta validator watches]
   IEquiv
   (-equiv [o other] (identical? o other))
@@ -7124,21 +7127,33 @@ reduces them without incurring seq initialization"
         new-value))
     (-reset! a new-value)))

+;; generic to all refs
+;; (but currently hard-coded to atom!)
+(defn deref
+  [o]
+  (-deref o))
+
 (defn swap!
   "Atomically swaps the value of atom to be:
   (apply f current-value-of-atom args). Note that f may be called
   multiple times, and thus should be free of side effects.  Returns
   the value that was swapped in."
   ([a f]
-     (reset! a (f (.-state a))))
+     (if (instance? Atom a)
+       (reset! a (f (.-state a)))
+       (-swap! a (deref a))))
   ([a f x]
-     (reset! a (f (.-state a) x)))
+     (if (instance? Atom a)
+       (reset! a (f (.-state a) x))
+       (-swap! a (f (deref a) x))))
   ([a f x y]
-     (reset! a (f (.-state a) x y)))
-  ([a f x y z]
-     (reset! a (f (.-state a) x y z)))
-  ([a f x y z & more]
-     (reset! a (apply f (.-state a) x y z more))))
+     (if (instance? Atom a)
+       (reset! a (f (.-state a) x y))
+       (-swap! a (f (deref a) x y))))
+  ([a f x y & more]
+     (if (instance? Atom a)
+       (reset! a (apply f (.-state a) x y more))
+       (-swap! a (f (deref a) x y more)))))

 (defn compare-and-set!
   "Atomically sets the value of atom to newval if and only if the
@@ -7149,13 +7164,6 @@ reduces them without incurring seq initialization"
     (do (reset! a newval) true)
     false))

-;; generic to all refs
-;; (but currently hard-coded to atom!)
-
-(defn deref
-  [o]
-  (-deref o))
-
 (defn set-validator!
   "Sets the validator-fn for an atom. validator-fn must be nil or a
   side-effect-free fn of one argument, which will be passed the intended

这个问题不好解决,因为票据是双重性质的:性能是一个问题(虽然不清楚是什么问题:“原子运行得不够快”还是“使用reset!的其他东西运行得不够快”?),而且还存在设计问题(“我们需要一个IAtom协议”)。我认为问题在于,即使它们不是真正的原子,其他实现也必须承受验证和通知观察者的成本。但我希望情况更加清晰明了。

我对Clojure/Script中提交的内容之一不喜欢的是它们描述性不强。我希望它们更像内核,提供适当的背景信息,以便像我们这样试图找出事情发展过程的人们可以获得更有用的信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接