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

欢迎!有关此功能如何工作的更多信息,请参见 关于页面

+39
Clojure
编辑

我经常编写: (some #(when (pred %) %) ...) 并且经常错误地将: (some #(pred %) ...) 写成这样。我认为让 (some #(when (pred %) %) ...) 成为一个 Clojure.core 中的内置函数会有价值。

建议的名称: first-by。您也可以自由提出其他名称,我会在这里列出。

(first-by #(= (:id %) 2) [{:id 1} {:id 2} {:id 3}]) ;;=> {:id 2}

统计数据,我在本地的 .m2 目录中发现了 198 个 (some #(when ...) ..) 形式,以及 1503 个不是 when 的 (some #(foo ...) ...) 形式。

总的 some + fn 使用次数:1701,其中 11% 是 some + when 形式。

发现这些用法的程序

(ns grasp
  (:require
   [clojure.spec.alpha :as s]
   [grasp.api :as g]))

(s/def ::some+when
  (s/cat :some #{'some}
         :fn (s/spec (s/cat :fn #{'fn 'fn*}
                            :args vector?
                            :when (s/spec (s/cat :when #{'when} :whatever (s/* any?)))))
         :coll any?))

(defn keep-fn [{:keys [spec expr uri]}]
  (let [conformed (s/conform spec expr)]
    (when-not (s/invalid? conformed)
      {:expr expr
       :uri uri})))

(defn -main [& args]
  (let [classpath (first args)
        matches (g/grasp classpath ::some+when {:keep-fn keep-fn})]
    (prn (count matches))))

{:deps {io.github.borkdude/grasp {:mvn/version "0.0.3"}}}

在 grep.app 上,我发现大约 8% 的 some 用法是 some+when 形式。

7 个答案

+7

我更倾向于像在 这个评论 中提到的 (first xform coll) 这样。这个想法在那里被提到了,但我不认为当时已经完全讨论过。

这比 somefirst-by 更通用:(some <pred> <coll>) 仅仅是 (first (keep <pred>) <coll>),而 (first-by <pred> <coll>) 仅仅是 (first (filter <pred> <coll>))

此外,它还可能比 somefirst-by 更高效。如果您想与其他序列函数一起使用它,可以将它们统一到转换器中,比如

(first (comp (map #(* % %))
             (filter #(> % 100)))
       (range))

而不是

(first-by #(> % 100) (map #(* % %) (range)))
注意,我不想基于 filter 实现first-by,因为分块

https://twitter.com/borkdude/status/1567525617549152257
(first xform coll) 由于它会使用 filter 返回的转换器函数的arity,因此不会受到分块的影响。

巨大差异

(first (filter <pred> <coll>))

(first (filter <pred> <coll>))

编辑
是的,如果我们这样实现它

(defn first' [xf coll]
  (transduce xf (completing (fn [_ x] (reduced x))) nil coll))

它不会引起任何分块。实际上,它甚至没有意识到序列的存在。
啊,(完成简化),太聪明了!比我在 xforms 的基础上提出的建议简单得多。
对不起,我是指 `(completing (fn [_ x] (reduced x)))`。已修复。
+2

顺便说一句,一个类似的建议之前已被拒绝: https://clojure.atlassian.net/browse/CLJ-2056

Alex 的回复很有趣,我确实看到了他的观点。

话虽如此,尽管我同意线性搜索是不高效的,但这是在大数据集的情况下考虑的。我最常见的这类操作是在处理短序列时,例如处理文本行。单次查找的索引结构仍需要线性数据处理,而且对于短序列来说效率更低(与 ArrayMap 比较)。

如果某种不同的结构总是正确的方式,那么我们不会有一打人立即赞成这个任务,其中许多人评论说我们一直在这样做。
+2

我同意这样的观点:允许甚至支持线性搜索可能会让人一开始就使用错误的数据结构。

然而,对于一个小集合进行简单的线性搜索通常正是正确的事情,而在Clojure中做这个事情总有些尴尬。我认为这是一种不好的尴尬,它会导致不清晰和错误。

当我需要它时,通常会在某个实用命名空间中实现Common Lisp中的find-if,因为它使代码非常清晰。

(find-if odd? xs)

+1 投票

仅作一提,我更希望核心中有一个类似when-valid的,而不是first-by

(defn when-valid [pred]
  #(when (pred %) %))

它可以与some结合使用。

(some (when-valid pos?) [-1 0 2]) => 2

它也可以与some-fn一起使用。

((some-fn (when-valid string?) (when-valid pos?)) "foo") => "foo"
((some-fn (when-valid string?) (when-valid pos?)) -5) => nil

编辑
我最新版本的函数是`select`。

```
(defn select
  "如果`(pred x)`为逻辑真,则返回`x`,否则返回`nil`。
   如果参数为1个,则返回函数 #(select % pred)。
  ([pred]
   #(select % pred))
  ([x pred]
   (when (pred x) x)))

(some-> x (select number?) inc)

(keep (select pos?) xs)
```
+1 投票

编辑

使用cgrand/xforms,我通常用x/some的代码解决此问题(x/some (filter <pred>) coll),只要我要返回的项不是nil,这个方法就有效。

基于xforms代码的潜在实现可能看起来像这样

(defn rf-first
  "Reducing function that returns the first value."
  ([] nil)
  ([x] x)
  ([_ x] (reduced x))) 

(defn xf-first
  "Process coll through the specified xform and returns the first value."
  [xform coll]
  (transduce xform rf-first nil coll))

(xf-first (filter <pred>) coll)
这与分块操作如何协同? some + when 不支持分块
您在哪里看到分块操作了?
现在我觉得很有道理。
0

我每天都在做同样的事情!赞同。

我个人把这个函数叫作“find”(寻找),定义如下。这是我过去在其他语言和函数库中看到它被这么叫的。我认为“first”(第一个)是解决方案的一部分,但并不是我想用来命名的名称,因为它是filter+first的组合,它们都是相等的组成部分,所以我觉得名称应该描述两个的组合。
我选择了`ffilter`,因为我曾经用`filter`和`first`包裹的方式来实现它。
0

编辑过

如另一回答中所述,这在过去被考虑并拒绝。我认为自那以后没有什么变化。

关于这个问题的轶事并不特别有用,但数据是有用的。如果您能使用 grep.app 或 grasp 等工具收集使用数据,这将有所帮助。同样,拥有现有类似函数的通用库列表及其使用情况(以及如果存在,这些实现的不同之处)也很有用。

另外,从更好地考虑用法语料库的角度来考虑这个问题可能是有用的,看看是否存在更深入或更有趣的共同之处或问题,这些可能具有其他替代解决方案。

我正在处理一些数据。
将数据添加到原始帖子中。
...