解剖地图

Hen*_*gon 3 clojure map

我在地图中有原子,这可能是也可能不是一个好主意,但关键是我需要对原子进行deref,这样我就可以对地图进行json-str,并且json-str不能处​​理原子,所以我写了这个:

(defn deatomize- [m]
 (cond
   (instance? clojure.lang.Atom m) #(deatomize- @m)
   (map? m) (zipmap (keys m) (map #(trampoline deatomize- %) (vals m)))
   :else m
 )
)

(defn deatomize [m] (trampoline deatomize- m))
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但a)它是好的,b)有更好的方法吗?

mik*_*era 11

我认为你的代码可以正常工作.

一些一般反馈:

  • 地图中的原子有点反模式 - 通常你会尝试将地图放在原子中.我们的想法是尽可能地保持您的数据结构不可变,并让您的引用在相当精细的级别上保存整个数据结构.我强烈建议重新考虑这个设计 - 从长远来看它可能会伤到你(例如,大多数Clojure库函数都会假设不可变的地图)
  • 你没有对钥匙进行解密.也许你永远不会在键中使用原子,在这种情况下,这很好,但认为值得指出.
  • 通常在Clojure中,关闭括号不会得到自己的行,而是在前一行的末尾.起初这可能会让人觉得奇怪,但你应该采用的是很好的Lisp风格.
  • 这个函数的性能可能不是那么好,因为它正在逐个重建整个地图结构,即使没有任何变化.通过仅在需要时更改地图,可以使用reduce来改善这一点.
  • 我认为最好测试clojure.lang.IDeref而不是clojure.lang.Atom.通过这样做,您的代码也将处理其他引用类型(如果需要).
  • trampoline会增加开销,只需要在(可能是罕见的?)情况下,你的数据结构中有很深的嵌套,可能会导致堆栈溢出.如果您需要安全堆栈处理和良好性能,那么您可能会考虑递归实现,并在StackOverflowException的情况下回退到trampoline版本.
  • 如果找到一个原子,该函数实际上是尾递归的,所以你可以使用recur.这将减少使用蹦床的需要!

这是另一种考虑因素:

(defn deatomize [m] 
  (cond
    (instance? clojure.lang.IDeref m) 
      (recur @m)
    (map? m) 
      (reduce 
        (fn [current-map [k v]] 
          (let [nv (deatomise v)] 
            (if (= v nv) current-map (assoc current-map k nv)))) 
        m 
        m)
    :else m))
Run Code Online (Sandbox Code Playgroud)