函数在原子内部变化的哈希码.为什么发生这种情况



作为我正在研究的数据可视化应用程序的一部分,我遇到了一些奇异的错误,或者我从根本上不了解某些东西。

我的应用程序具有代表ColorsCales的数据结构的代码,并将它们转换为具有数字并返回颜色RGB值的功能。

实现了梯度和范围Colorscales:

{:type :gradient
 :scale [{:bound 0 :r 0 :g 0 :b 0}
         {:bound 1 :r 255 :g 0 :b 0}
         {:bound 2 :r 0 :g 255 :b 0}]}
{:type :range
 :scale [{:bound [[< 0]] :r 250 :g 250 :b 250}
         {:bound [[>= 0] [< 1]] :r 0 :g 0 :b 0}
         {:bound [[>= 1] [< 2]] :r 255 :g 0 :b 0}
         {:bound [[>= 2]] :r 0 :g 255 :b 0}}]

有一些功能将这些功能转化为函数,其用法类似于以下功能:

((create-colorscale-fn **GRADIENT-MAP**) 1.5) => {:r 128 :g 128 :b 0}
((create-colorscale-fn **RANGE-MAP**) 1.5) => {:r 255 :g 0 :b 0}

也有两个函数也可以在两者之间进行转换,但这是与我的帖子相关的功能:

(defn- gradient-colorscale-to-range
  [in]
  {:pre [(verify-gradient-colorscale in)]
   :post [(verify-range-colorscale %)]}
  {:type :range
   :scale (into []
        (concat
         (let [{:keys [bound]} (-> in :scale first)
               {:keys [r g b]} {:r 250 :g 250 :b 250}]
           [{:bound [[< bound]] :r r :g g :b b}])
         (mapv (fn [[a {:keys [r g b]}]] {:bound a :r r :g g :b b})
               (partition 2 (interleave
                     (map (partial apply vector)
                      (partition 2
                             (interleave
                              (map #(vector >= (:bound %)) (-> in :scale))
                              (map #(vector < (:bound %)) (-> in :scale rest)))))
                     (-> in :scale))))
         (let [{:keys [bound r g b]} (-> in :scale last)]
           [{:bound [[>= bound]] :r r :g g :b b}])))})

"验证范围颜色刻板"功能的一部分测试了以下条件有关不等式操作员的条件:

(every? #{< <= > >=} (map first (mapcat #(-> % :bound) (:scale in))))
 ;;Each bound must consist of either <= < >= >

这是我的问题所在:

由于某种原因,大多数时候,当我运行此功能时,它不会给我任何问题,并且适当的不平等运算符的测试按应有的运行:

(def gradient
    {:type :gradient
    :scale [{:bound 0 :r 0 :g 0 :b 0}
             {:bound 1 :r 255 :g 0 :b 0}
             {:bound 2 :r 0 :g 255 :b 0}]})
 (#{< <= > >=} (get-in (gradient-colorscale-to-range gradient) [:scale 0:bound 0 0])) 
     => #object[clojure.core$_LT 0x550b46f1 "clojure.core$_LT_@550b46f1

然而,色彩标准设置在一个原子中,其内容在全局变量内找到。我已经将编辑器开发为将ColorsCale状态的一部分复制到另一个原子中,然后使用图形编辑器进行编辑。当我将梯度转换为原子内部的范围时,将原子的内容与全局原子相关联,然后检查操作员的平等性,出于某些奇异的原因,测试失败了。

 (#{< <= > >=} (get-in (gradient-colorscale-to-range gradient) [:scale 0:bound 0 0])) 
     => nil

当我检查为什么失败时,似乎在原子更新期间的某个时刻,功能少的哈希代码会发生变化。

(mapv #(format "%x" (.hashCode %)) [< (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0])])
   -> ["550b46f1" "74688dde"]

并将包含设置显然基于其哈希码测试功能,这会导致我的"验证范围 - 彩色尺度"测试失败。

所以问题是,为什么在原子更新期间,我的不平等函数的哈希码会更改?这是clojure.core中定义的功能,但似乎在某个时候制作了它的副本?


对Piotrek的响应:

数据结构存储在命名空间" INAV"中的全局原子中。

加载&lt;:

的主题时
 (format "%x" (.hashCode <)) => "425b1f8f"

更改显示在显示配置原子中的颜色尺度时,使用转换功能:

 (swap! xmrg-cache update-in [[0 0] :colorscale gradient-colorscale-to-range)
 (format "%x" (.hashCode (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0]))) => "425b1f8f"

有一个图形颜色尺度编辑器,该编辑器在更新活动配置之前使用一系列手表来编辑临时副本。它是通过单击ColorsCale预览图像来启动的:

  (.addMouseListener colorscale-img-lbl
     (proxy [MouseAdapter] []
        (mouseClicked [me]
           (let [cscale-atom (atom (get-in @xmrg-cache [(find-pane-xy e) :colorscale]))]
              (add-watch cscale-atom :aoeu
                 (fn [k v os ns]
                     (swap! xmrg-cache assoc-in [(find-pane-xy parent-e) :colorscale] ns)
                     (redrawing-function)))
              (launch-colorscale-editor cscale-atom other-irrelevant-args))))

然后,启动色彩编辑器有很多选项,但是相关的部分是转换组合和应用按钮:

(defn- launch-colorscale-editor [cscale-atom & other-irrelevant-args]
  (let [tmp-cscale-atom (atom @cscale-atom)
        convert-cb (doto (JComboBox. (to-array ["Gradient" "Range"]))
                      (.setSelectedItem ({:range "Range" :gradient "Gradient"} (:type @tmp-cscale-atom)))
        apply-button (JButton. "Apply")]
     (add-action-listener convert-cb
         (fn [] (let [prev-type (:type @tmp-cscale-atom)
                      new-type ({"Gradient" :gradient "Range" :range} (.getSelectedItem convert-cb))]
                   (when (not= prev-type new-type)
                     (case [prev-type new-type]
                           [:gradient :range] (swap! tmp-cscale-atom gradient-colorscale-to-range)
                           ;other options blah blah
                      )))))
     (add-action-listener apply-button
        (fn [] (reset! cscale-atom @tmp-cscale-atom)
               (redrawing-function))))

基本上,当您单击"应用"时,您将tmp-cscale-atom的内容(#'iNav/create-colorscale-editor内部(复制到cscale-atom(#ploslock of#of#'的内部(Inav/More-Grid-Options-dialog(,它触发一款手表,该手表自动将ColorsCale从CSCALE-ATOM复制为XMRG-CACHE(全球定义的#'Inav/XMRG-Cache(。

在这种方式编辑时,&lt;最终成为这个

(format "%x" (.hashCode (get-in @xmrg-cache [[0 0] :colorscale :scale 0 :bound 0 0]))) => "5c370bd0"

关于此行为的最后说明:

当您从应用按钮动作侦听器中调用"重绘 - 功能"时,尝试验证范围ColorsCale的尝试是成功的。

随后从applatut button操作听众外出调用"重绘 - 功能"时,尝试验证范围ColorsCale失败的尝试。

...我只是弄清楚了这个问题,当我刷新ColorsCale时,我将重新评估ColorsCale作为我的重新验证功能的一部分。这正在使事情变得混乱。

clojure中的功能是实现clojure.lang.IFn接口的常规Java对象。当您加载名称空间(包括clojure.core(时,Clojure将编译功能(生成新的Java类,创建其实例,然后将该实例分配为VAR值(。例如,#'clojure.core/< VAR将获得一个新的Java对象,该对象实现了clojure.lang.IFn,恰好比逻辑不太少。

clojure不会覆盖生成的函数类中的hashCode实现,因此从java.lang.Object继承了默认的功能。因此,每个新实例都有其潜在的不同哈希码。这引起了您的问题:当命名空间重新加载时,VAR将获得新功能实例,从而获得不同的哈希码。

另一方面,我将检查您的测试的工作方式:

  • 在您的测试执行过程中是否有任何名称空间?
  • 您是否存储全球状态(例如,在全球原子中<功能(在测试功能范围之外?

也许您应该使用本地范围来代替测试功能中的预期值?

我已经能够通过明确重新加载clojure.core来重现部分行为的部分当clojure.core时,该功能不会更改。

user> (.hashCode <) 
87529528
;; jump to clojure.core and reload namespace
user> (.hashCode <) 
228405583
user> (.hashCode #'<) 
1242688388
;; jump to clojure.core and reload namespace
user> (.hashCode #'<) 
1242688388

我不能使用您的代码,即您的编辑过程中会发生什么可能导致这些表格重新评估,因此可能还有其他原因。一个解决方法可能是将包含测试功能的VAR存储在地图中,而不是直接将功能对象存储。您可以使用#'读取器 - 麦克罗。

将var称为函数会自动调用var中的函数,因此不需要其他地方的更改。

偶然地,我注意到一个相关行为只是上周。当您定义相同的功能时,它们不会获得相同的哈希码:

(defn ink [x] (+ 1 x))
(spyx (hash ink))
(spyx ink)
(defn ink [x] (+ 1 x))
(spyx (hash ink))
(spyx ink)
(hash ink) => 539734147
ink => #object[tst.clj.core$ink 0x202bb083 "tst.clj.core$ink@202bb083"]
(hash ink) => 757183584
ink => #object[tst.clj.core$ink 0x2d21b460 "tst.clj.core$ink@2d21b460"]

因此,每个 defn都使用新的hashcode生成一个新功能对象(实际上,函数对象标签0x202bb083只是哈希539734147的HEX值(。此行为与创建两个单独的Java Object实例时所见的行为相同:

(hash (Object.)) => 1706817395
(hash (Object.)) => 969679245

回想一下Object.hashcode()的默认实现就是简单地从对象的内存地址派生一个整数。

因此,结果是,即使它们相同,我们也无法比较功能对象。因此,我们需要一个解决方法,将令牌作为映射密钥和功能实例作为相应的映射值。这是一种方法:

(defn ink [x] (+ 1 x))
(defn dek [x] (- x 1))
(def sym->fn {'++ ink
              '-- dek})
(defn runner [form]
  (let [[fn-symbol val] form
        fn-impl         (get sym->fn fn-symbol)
        result          (fn-impl val)]
       result))
(runner '(++ 2)) => 3
(runner '(-- 5)) => 4

相关内容

最新更新