请在 2024 年 Clojure 情况调查! 中分享您的想法。

欢迎!请查看 关于 页面了解此操作的一些更多信息。

+1 投票
集合

我有一些代码用于从一个格式转换为另一个数据结构(从 SVG Hiccup 属性到 CLJFX 属性)。这两种格式非常相似,但当有属性存在时,我需要偶尔对其进行一些修改。例如,可能需要将字符串(read-string ..)转换成数字,或者更新关键字以便以不同的方式拼写等。

目前我的代码是这样的

(-> svg-attributes
  (update :stroke #(case %
                     nil :black
                     "none" :black
                     (if (= \# %)
                       %
                       (keyword %))))
  (update :fill #(case %
                   nil "transparent"
                   "none" "transparent"
                   (if (= \# %)
                     %
                     (keyword %))))
  (update :points #(case %
                     nil []
                     [] []
                     (map read-string (-> %
                                          (clojure.string/split #"[ ,]")))))
  (update :stroke-width #(case %
                           nil 1.0
                           %))
  (update :stroke-dasharray #(case %
                               nil []
                               (map read-string (-> %
                                                    (clojure.string/split #"[ ,]")))))
  (clojure.set/rename-keys {:stroke-dasharray :stroke-dash-array})
  (update :font-size #(case %
                        nil 10
                        "none" 10
                        %))))

问题是,我不断地需要捕获 nil 的情况并添加默认值...这大多数情况下都奏效,但我想在没有要更新内容的情况下跳过更新。

我看到了这个问题,它与这个问题非常相似:https://ask.clojure.org/index.php/8387/how-to-avoid-nil-values-in-maps

(cond-> 和在每个行上 (some? (:somekeyword %)) 来进行这种操作是习惯用法吗?我觉得我没有找到合适的工具!也许有人能给出更好的建议 :)

4 个答案

+1 投票

我在几个项目中看过 update-if-exists 的手写变体,下面是 medley 实用库中示例实施的一个例子。

据我所知,Clojure 核心函数库中没有这样的工具。

by
这正是我所怀疑的。我觉得最好再检查一遍。有时候我会看到一些地图处理的杂技,然后我就会学到一个新的绝招 :)
+1 投票
by

update-if-exists 是值得关注的知识,但在这里并不是完全切中要害...

原始方法的主要和根本的问题是它把每个键的逻辑和地图更新逻辑结合在一起,从而掩盖了翻译的本质(例如,键和值都发生变化)以及翻译之间的相互关系(后来的翻译是否依赖于之前的翻译)。

将两个关注点分开,每一个都会更容易改进。

要翻译一个属性,你可以有一个函数(或许是一个多方法),或者一个查找表。例如,假设你有一个函数 svg->cljfx,它接受一个 [k v](例如,一个地图条目)并生成一个新的 [k v];然后可以通过以下方式翻译整个 svg-attributes

(->> svg-attributes
     (map svg->cljfx)
     (into {}))

易于阅读,但如果未更改的属性数量多于已更改的属性,则不太高效。

或者,假设你有一个 SVG 键到函数(可能是匿名函数)的查找表(也就是映射),生成一个新的 [key value] 对。一个循环可以遍历已知的转换。它需要跳过缺少的键,但至少可以一次性涵盖这个情况。逻辑会更复杂,但它可能运行得更快。

by
顺便说一句  你使用的是 `edn/read-string`,是吗?  核心中的 `read-string` 值得避免,除非你真的希望它起副作用。
by
哦,映射到键值对上真是巧妙!我完全忘记可以用`map`这样用了!感谢建议。虽然多方法会稍微冗长,因为每次它只返回一个键。然而,它比我原来的方法要干净。
0

如果所有默认值都是“值”,且函数处于尾递归位置,这将这样做

(defn set-defaults [v m f]
  (or (get m v) (f v)))

(defn points [s]
  (mapv read-string (clojure.string/split s #"[ ,]")))

(-> {:points    "1,2,3,4"
     :stroke    \#
     :font-size "none"}
  (update :stroke set-defaults {nil :black "none" :black \# \#} keyword)
  (update :fill set-defaults {nil "transparent" "none" "transparent" \# \#} keyword)
  (update :points set-defaults {nil [] [] []} points)
  (update :stroke-width set-defaults {nil 1.0} identity)
  (update :stroke-dasharray set-defaults {nil []} points)
  (update :font-size set-defaults {nil 10 "none" 10} identity)
  (clojure.set/rename-keys {:stroke-dasharray :stroke-dash-array}))

;=>
{:points            [1 2 3 4]
 :stroke            \#
 :font-size         10
 :fill              "transparent"
 :stroke-width      1.0
 :stroke-dash-array []}

或者,如果任何默认值也可以是函数

(defn set-defaults2 [v m defaultf]
  (let [f (get m v defaultf)]
    (f v)))

(-> {} 
  (update :stroke set-defaults2 {nil (constantly :black)}))

当然,`condp` 中还有 `:>>` "特性":[示例542692cbc026201cdc326be4](https://docs.clojure.org/clojure.core/condp#example-542692cbc026201cdc326be4)

0

如果你也同意更新等于 `nil` 的现有值

(update your-map (fnil identity :default-value))
...