数据查询的 Clojure prewalk 无限递归

Clojure prewalk infinite recursion for datomic queries

我需要动态修改这个结构的数据:

[:db/id
 :list/title
 :list/type
 {:list/items [... lots of nested data ...]}]

以下内容:

[:db/id
 :list/title
 :list/type
 {(default :list/items []) [... lots of nested data ...]}]

由于我正在处理多个不同的查询,因此我可以确定连接将是向量中的第四项。但是我需要用 (default :list/items []).

替换 :list/items 的每个实例

我知道的唯一方法是使用 clojure.walk/prewalk。然而,它会导致无限递归:

(clojure.walk/prewalk #(if (= :list/items %)
                        '(default :list/items [])
                        %) 
                      query)

一旦 walk 找到 :list/items 并将其替换为 '(default :list/items []),它就会在替换值中找到 :list/items,并将其替换。等等等等。

我可以使用原子来确保值只被替换一次,但这感觉就像作弊。

还有其他方法吗?

在这种情况下,您可能必须使用 postwalk:

user> 
(def query [:db/id
            :list/title
            :list/type
            {:list/items [:db/id
                          :list/title
                          :list/type
                          {:list/items []}]}])
#'user/query

user> (clojure.walk/postwalk #(if (= :list/items %)
                               '(default :list/items [])
                               %) 
                             query)
[:db/id :list/title :list/type 
 {(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}]

postwalk 不会深入到内容,即使叶子已被新集合替换:

user> (clojure.walk/prewalk #(do (println %)
                                 (if (= % 1) [10] %))
                            [[1 2 3 [1 2]] [1 2]])
[[1 2 3 [1 2]] [1 2]]
[1 2 3 [1 2]]
1
10 ;; goes deeper
2
3
[1 2]
1
10 ;; and here
2
[1 2]
1
10 ;; and here
2
[[[10] 2 3 [[10] 2]] [[10] 2]]

user> (clojure.walk/postwalk #(do (println %)
                                  (if (= % 1) [10] %))
                             [[1 2 3 [1 2]] [1 2]])
1
2
3
1
2
[[10] 2]
[[10] 2 3 [[10] 2]]
1
2
[[10] 2]
[[[10] 2 3 [[10] 2]] [[10] 2]]
[[[10] 2 3 [[10] 2]] [[10] 2]]

顺便说一句,有一个很好的函数 prewalk-replace/postwalk-replace 适合您的具体情况:

user> (clojure.walk/postwalk-replace 
        {:list/items '(default :list/items [])} query)

[:db/id :list/title :list/type 
 {(default :list/items []) [:db/id :list/title :list/type {(default :list/items []) []}]}]

更新,评论后: 一些(合成的)例子可以更好地控制替换。假设您想替换某个任意嵌套向量集合中的特定项目,但只替换该项目一次(第一次看到它时),其余部分保持不变:

user> (require '[clojure.zip :as z])

user> 
(defn replace-once [rep coll]
  (loop [curr (z/vector-zip coll) rep rep]
    (if (empty? rep) (z/root curr)
        (let [n (z/node curr) r (rep n)]
          (cond (z/end? curr) (z/root curr)
                r (recur (z/replace curr r) (dissoc rep n))
                :else (recur (z/next curr) rep))))))
#'user/replace-once

user> (replace-once {1 100 2 200} [[4 3 2] [10 1 2] 1 2 [5 3 2]])
[[4 3 200] [10 100 2] 1 2 [5 3 2]]

(这里你只是从替换候选映射(rep)中删除替换的项目,并通过递归进一步传递它,直到它为空)