2024 年 Clojure 调查 中分享您的想法!

欢迎!请参阅关于页面了解有关此信息的工作原理的更多信息。

+1
Collections

我有一些代码,用于将一种数据格式转换为另一种格式(从 SVG hiccup 属性转换为 CLJFX 属性)。格式相当相似,但当一个属性存在时,有时需要对它稍作处理。例如,可能需要将字符串读取为数字,或更新一个关键字使其拼写有点不同,等等。

现在我的代码是这样的

(-> 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 工具库中的一个示例实现。

AFAIK,Clojure 内核中不存在此类内容。

这正是我所怀疑的。我想再次确认一下。有时候我会看到一些地图处理的技巧,我会学到一些新招式 :)
+1

了解“update-if-exists”是好的,但在这里,它不会完全解决问题。

原始程序的主要问题是其复杂性,它混淆了每键逻辑和更新地图逻辑,因此掩盖了翻译的本质(例如,键和值都发生变化)以及翻译之间的关系(后续翻译是否依赖于早期翻译)。

将这两个问题分开,每个问题都更容易改进。

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

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

容易阅读,但如果不变的属性多于变化的属性,那么它在CPU效率方面并不是很出色。

或者,假设你有svg键到一个函数(可能是匿名函数)的查找表(映射),它产生一个新 [key value] 对。循环可以迭代已知的转换。它必须跳过丢失的键,但至少可以一次性覆盖这种情况,而不是每次键进行处理。逻辑会更复杂,但它可能运行得更快。

附注:你使用的是 `edn/read-string` 吧? 核心中的 `read-string` 最好避免,除非你真的想要它用于副作用。
哦,在键值对上进行映射很巧妙!我完全忘记了可以使用`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`中还有一个`>>`“功能”:[https://docs.clojure.org/clojure.core/condp#example-542692cbc026201cdc326be4](https://docs.clojure.org/clojure.core/condp#example-542692cbc026201cdc326be4)

0

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

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