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个 (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 + when形式的使用。

7 答案

+7

我更愿意在评论中提到的CLJ-2056提到的东西中看到 (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)))
by
注意,我不想基于 filter 来实现 first-by,因为分块

https://twitter.com/borkdude/status/1567525617549152257
by
(first xform coll) 不会受到分块的影响,因为它将使用 filter 返回的转换器算子。

差异很大

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

(first (filter <pred> <coll>))
by
edited by
是的,如果我们这样实现

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

它将不会导致任何分块。事实上,它甚至没有意识到序列。
by
啊,完成了简化(completing reduced),天才!比我基于 xforms 提出的建议简洁多了。
对不起,我本意为 `(completing (fn [_ x] (reduced x)))`。现在已修复。

顺便提一下,以前曾拒绝过类似的建议: https://clojure.atlassian.net/browse/CLJ-2056

Alex 的回复很有趣,我确实赞同他的观点。

话虽如此,尽管我同意在线性搜索数据集较大时效率不高,但在我最常使用这个操作的环境中,通常是处理短序列,例如在处理文本行时。为了单个查找构建索引结构仍然需要对数据进行线性处理,并且在短序列的情况下效率大大降低(与 ArrayMap 比较)。

如果另一结构总是正确的,那么我们就不会有几十个人立即对这个票据进行投票,其中多个人评论说他们一直在这样做。

我理解这个观点,即允许甚至支持线性搜索可能会让人们一开始就使用错误的数据结构。

然而,经常情况下,对于一个小集合进行简单的线性搜索正是正确的事情,但在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`。
   如果是一元函数,返回函数`(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)
这种实现与分块(chunking)的性能如何? some + when 不进行分块
你在这个地方看到分块了吗?
没有任何地方,现在对我来说有道理。
0

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

我亲自把这个函数称为“find”,并定义为这样。这是我以前在其他语言和函数库中看到的做法。我认为“first”是解决方案的一部分,但并不是立即想到的名字,因为它是filter+first,两者都是相等的组件部分,所以我觉得名字应该描述混合两者的组合。
我选择了`ffilter`,因为我的另一种做法是将`filter`包装在`first`中。
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)

* `find-first` 的实现有 24 种:[链接](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)

`(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)

`(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)

`filter-first` 的实现仅有少数:[链接](https://grep.app/search?q=filter-first&regexp=true&filter[lang][0]=Clojure)

定义为 clojure-contrib 的序列函数,但你肯定知道这些。
...