2024Clojure状态调查!分享你的想法。

欢迎!请在关于页面查看更多关于如何使用本站的信息。

+1投票
Collections

我有一些代码,它将数据结构从一种格式转换为另一种格式(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实用库中的一个示例实现。

AFAIK,Clojure核心中不存在此类东西。

by
这就是我猜测的。我想再确认一下。有时我会看到一些地图处理的组合技,然后我就能学到新技巧 :)
+1投票
by

update-if-exists 是个应该了解的特性,但在这里并不完全解决问题..

原始方法的主要和根本问题在于它将每个键的逻辑和更新逻辑交织在一起,从而模糊了翻译的本质(例如,键和值都发生变化)以及翻译之间相互关系(是否后续翻译依赖于先前的翻译)。

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

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

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

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

或者,假设你有一个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`中有一个 `:>>` "特性":https://docs.clojure.org/clojure.core/condp#example-542692cbc026201cdc326be4

0

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

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