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

欢迎!请在 关于 页面查看更多有关此工作的信息。

+39
Clojure
编辑

我经常写: (some #(when (pred %) %) ...),并经常错误地写成: (some #(pred %) ...)。我认为在 clojure.core 中添加 (some #(when (pred %) %) ...) 会很有用。

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

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

统计,在我的本地 .m2 目录中,我找到了 198 个 (some #(when ...) ..) 形式和 1503 个 (some #(foo ...) ...) 形式,其中 foo 不是 when。

总 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),在CLJ-2056。这个想法被提到了那里,但我认为当时并没有完全讨论。

这比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
by

仅做笔记,我更希望核心中使用 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
by
修改 by
我最新版本的这样一个函数是 `select`

```
(defn select
  "如果 `(pred x)` 是逻辑真,则返回 `x`,否则返回 `nil`。
   在 1-arity 的情况下返回函数 #(select % pred)。
  ([pred]
   #(select % pred))
  ([x pred]
   (when (pred x) x)))

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

(keep (select pos?) xs)
```
+1
by
修改 by

使用 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)
这个模型的 chunking 如何? some + when 不支持 chunking。
你在哪里看到 chunking 的?
没有,现在的感觉很有道理。
0 投票

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

我将这个函数个人称作“查找”,并这样定义它。这是我在其他语言和功能库中看到的方式来称呼它。我认为“第一个”是解决方案的一部分,但并不是名字来的那样,因为它既有filter又有first,它们都是相等的组成部分,所以我感觉名称应该描述这两个部分的组合。
因为我之前曾通过将`filter`与`first`包裹在一起来做,所以我选择了`ffilter`。
0 投票

就像在其他回答中提到的,过去确实考虑过这个问题,但后来拒绝了。我认为从那时起并没有什么改变。

关于这个话题的轶事可能并不特别有用,但数据是有用的。如果你能使用grep.app或grasp或其他工具收集使用数据,这将是有帮助的。还有一个有用的点是列出常见工具库中现有的类似功能及其使用情况(以及如果有的话,这些实现之间的差异)。

另外,可能有用的是从更好的考虑使用情况集合的角度来处理这个问题,看看是否存在更深或更有趣的共性或问题,这可能有其他替代解决方案。

我现在正在处理一些数据。
将数据添加到原始帖子中。
grep.app 搜索结果

* 检索 `find-first` 使用次数:超过 200 次:[https://grep.app/search?q=%5C%28%28%5Ba-zA-Z-%5D%2A/%3F%29%3Ffind-first%5B%5E-%5D&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=%5C%28%28%5Ba-zA-Z-%5D%2A/%3F%29%3Ffind-first%5B%5E-%5D&regexp=true&filter[lang][0]=Clojure)

* `find-first` 的实现数量:24 个:[https://grep.app/search?q=defn.%2Afind-first%5B%5E-%5D&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=defn.%2Afind-first%5B%5E-%5D&regexp=true&filter[lang][0]=Clojure)

* 有几个 `ffilter` 实现:[https://grep.app/search?q=%5C%28defn.%2Affilter&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=%5C%28defn.%2Affilter&regexp=true&filter[lang][0]=Clojure)

* `(some #(when pred %) coll)` 使用次数超过 100 次:[https://grep.app/search?q=%5C%28some%20%23%5C%28when%20.%2A%20%25%5C%29&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=%5C%28some%20%23%5C%28when%20.%2A%20%25%5C%29&regexp=true&filter[lang][0]=Clojure)

* 对于 `(reduce (fn [_ cur] (when (pred cur) (reduced cur)) coll)` 的一小部分用法:[https://grep.app/search?q=%5C%28reduce.%2A%5C%28when.%2A%5C%28reduced&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=%5C%28reduce.%2A%5C%28when.%2A%5C%28reduced&regexp=true&filter[lang][0]=Clojure)

* `filter-first`的一少部分实现:[https://grep.app/search?q=filter-first&regexp=true&filter[lang][0]=Clojure](https://grep.app/search?q=filter-first&regexp=true&filter[lang][0]=Clojure)

* 作为 clojure-contrib seq 函数定义,但您可能已经知道这一点 ;): [https://github.com/richhickey/clojure-contrib/blob/40b960bba41ba02811ef0e2c632d721eb199649f/src/main/clojure/clojure/contrib/seq.clj#L179](https://github.com/richhickey/clojure-contrib/blob/40b960bba41ba02811ef0e2c632d721eb199649f/src/main/clojure/clojure/contrib/seq.clj#L179)
...