为什么ClojureScript原子没有实现完整的协议?

Mar*_*rio 8 clojure clojurescript

swap!函数是Clojure工具箱中最惯用的工具之一,可以进行instance?检查.我们被告知在编程中避免实现类型检查的条件,更喜欢多态(协议).奇怪的是,ClojureScript没有ISwap直接针对原子实现协议,而是在swap!检查主题是否是原子之后,在公共api 中反而回落协议.

我认为这种策略必须用于性能原因,因为原子是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))))
Run Code Online (Sandbox Code Playgroud)

Joh*_*ter 5

似乎与性能有关:http : //dev.clojure.org/jira/browse/CLJS-760

添加带有-reset的IAtom协议!方法和cljs.core / reset!中Atom的快速路径!

在此处查看jsperf- http: //jsperf.com/iatom-adv

最新的chrome和firefox版本的速度会降低20-30%。较旧的Firefox版本遭受60-70%的损失。

Further down the ticket, it was decided to split IAtom into two protocols: IReset and ISwap. But this was the implementation that David went with, which does the type checking, and I imagine is was done to get the speed back up.

Unfortunately, it's not clear why Atom wasn't made to implement IReset (and ISwap) for that matter, nor why those things weren't looked for instead. And it's not clear how the original patch worked either. It basically took the implementation of reset! and put it under an instance check, and added the -reset! path for it:

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:
Run Code Online (Sandbox Code Playgroud)

This was committed in 33692b79a114faf4bedc6d9ab38d25ce6ea4b295 (or at least something very close to it). And then the other protocol changes were done in 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
Run Code Online (Sandbox Code Playgroud)

票证是双重性质的,这无济于事:性能是一个问题(尽管不清楚以什么方式:“原子运行速度不够快”,或者“其他使用重置的东西运行速度不够快”?)和设计问题(“我们想要IAtom协议”)。我认为问题在于,即使其他实现不是真正的原子,其他实现也必须进行验证并通知观察者成本。我希望情况更清楚。

我对Clojure / Script中的提交不满意的一件事是它们的描述性不是很高。我希望他们在适当的背景信息下更像是内核,以便像我们这样的人们试图弄清楚事情将如何发展,并具有更多有用的信息。