2024 Clojure 工作状况调查 中分享您的想法!

欢迎!请查看 关于 页面以获取更多有关其工作方式的信息。

+6
Collections
重新标记

在 `update`、`swap!` 等中的 `& args` 参数非常易于使用,且易于组合,所以我很惊讶地了解到 `update-keys` 和 `update-vals` 并没有那个功能,它们仅是 `[m f]`。

是什么原因导致这种情况的?

我认为将这些函数中的 `& args` 参数添加上会让它们与现有的核心函数更加一致,并且可以避免不必要的匿名函数。

1 个答案

+1

现有实用库中存在许多类似于 `update-keys` 和 `update-vals` 的函数(通常称为 `map-keys` / `map-vals`),据我所知,这些函数没有 `update` 额外的参数语义,并且这并非人们所请求的功能。

一个区别是,`update` 等调用 f 只对一个值执行,而 `update-keys` 和 `update-vals` 则对多个值调用 f。我不确定你会在多频繁地想要把相同的静态尾部参数传递给应用在每个键或值上的函数。我没有这样的例子(但这也许正是因为现有的函数目前还没有这样做)。

一个解决方案是当然的,将这个参数推进 f 通过 partial 或者匿名函数,所以这也许是可以搜索的东西。

你有没有特别的应用案例需要这个功能?

by
你可以通过以下方式找到一些例子

* https://grep.app/search?q=map-keys%20%23%28&filter[lang][0]=Clojure
* https://grep.app/search?q=map-vals%20%23%28&filter[lang][0]=Clojure

其中一些可能不包括需要的模式,但肯定有一些符合。
by
编辑 by
因为实用库将这些处理成序列函数,所以元数无法用于添加 `& args`,我认为这可能是原因之一。

我的具体用例是在清理一个原子中嵌套结构中的记录时,我在完成记录后想要保留其余数据。例如

```
(def *state (atom {"table" {:schema (,,,) :records [,,,]}}))

;; 我没想到这会失败。
(swap! *state update-vals assoc :records [])

;; 这有效,但不是很优雅。
(swap! *state update-vals #(assoc % :records []))
```

我发现这些例子很有用

* https://github.com/penpot/penpot/blob/develop/frontend/src/app/worker/import.cljs#L351
* https://github.com/penpot/penpot/blob/develop/backend/src/app/rpc/queries/files.clj#L273
* https://github.com/clojure/tools.analyzer/blob/master/src/main/clojure/clojure/tools/analyzer/passes/uniquify.clj#L31
* https://github.com/exoscale/deps-modules/blob/master/src/exoscale/deps_modules.clj#L154
* https://github.com/exoscale/deps-modules/blob/master/src/exoscale/deps_modules.clj#L156

在实际情况中,`update-vals` 比 `update-keys` 更有用,但对于一个设想示例,你可能希望将某个后缀添加到映射的键中。

```
(update-keys {"table" {}} str "__v1") => {"table_v1" {}}
````
by
编辑 by
[编辑:如何在网站上正确格式化代码?我试了,但做不到,所以就直接用markdown格式了。]

你是否仍然考虑添加 `update-keys` 和 `update-vals` 的可变参数情况?我在发现我的用例不受支持后找到了这个帖子,我认为我有一个很有说服力的例子表明这是必要的。

我有一个这样的数据结构(稍后还要用到的一个谓词)
```
(def data {:foo {0 {:bar [10 42 11 12]}
                 1 {:bar [20 21 42 22]}
                 ,,, }})

(def my-special-pred (complement #{42}))
```

(注意,`(data :foo)` 可以有任意多的项目;这三个逗号应该是省略号的样子。)

假设我想更新 `:foo 0 :bar` 的向量。这可以用以下方式完成:
```
(update data :foo update 0 update :bar (partial filter my-special-pred))
```

在这种情况下,你也可以直接使用 `update-in`(你大概也应该这么做)。但如果你想对所有 `:foo` 映射中的值进行操作,而不仅仅是0号位置的值呢?你应该能够直接使用
```
(update data :foo update-vals update :bar (partial filter my-special-pred))
```

但是你不能,因为 `update-vals` 只接受两个参数。相反,你需要做些像这样的事情:
```
(update data :foo update-vals #(update % :bar (partial filter my-special-pred)))
```

这比看上去更不方便;由于匿名函数字面量不允许嵌套,你不能使用另一个函数(例如)来进行谓词。但是,如果你有一个可变参数的 `update-vals`,你可以这样做:
```
(update data :foo update-vals update :bar (partial filter #(not= 0 (mod % 42))))
```

出于同样的原因,你不能简单地使用多个“层级”的 `update-vals`,如这样:
```
(def data2 {0 {:top 200
               :bottom 201
               ,,, }
            1 {:left 300
               :right 301
               ,,, }}
(update-vals data2 update-vals inc)
```

你可以用各种方法解决这个问题,但到目前为止,最干净、最好的办法似乎是编写自己的 `update-vals` 函数,该函数包装现有的函数。
```
(定义update-vals函数[d m f & args]
  使用clojure.core/update-vals m #(apply f % args))
```

定义好后,本段评注中的所有示例都按预期工作。由于这属于“累加式变更”(它是现有功能的严格超集),与其完全兼容,因此可以(应该)将其包含在核心库中。

(顺便提一句,`data2`示例是人为设定的,但另一个示例在结构上与我刚刚为我的项目编写的实际代码完全相同,我将会使用变长参数的`update-vals`包装器。)
by
Alex Robbins:重新格式化——你可以格式化问题和答案,但评论只是文本。
by
我今日在赞叹Clojure中更新函数的多重 arity 信号非常优秀,然后发现这个例子不支持。下面是问题所在

(defn scale [geometry factor]
  (update-vals geometry * factor))
by
我没有收到这些评论的邮件;Alex Miller也如此吗?我不想骚扰他,但我不知道是否应该通过其他渠道提出这个问题。
by
我正在收到邮件(以及每天大量其他问题的邮件,对不起但你是成千上万中的一员 :)。如果有足够的投票,我们将在未来考虑它。我们通常会根据投票计数和我们社区所看到的来定期评审这些问题。
...