请分享您的想法,参加 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)))
by
请注意,我不想基于 filter 来实现 first-by,因为 chunking

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

差异巨大

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

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

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

它将不会产生任何 chunking。事实上,它连序列都不需要。
啊,(完成化简),天才!比我基于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)
```
好了,这样人们也可以投这个帖子点赞 https://ask.clojure.org/index.php/8945/something-like-when-pred-in-the-core
+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或其他工具收集使用数据,那就很有帮助了。也有了现有的类似函数在通用库中的使用列表(以及这些实现如果不同它们是如何不同的)也同样有用。

另外,我们可以从更好考虑使用情况语料库的角度来考虑这个问题,看看是否存在更深层次的或更有趣的共同点或问题,这可能有其它的替代解决方案。

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

* 200+ 使用例证 `find-first`: [链接](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)

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

100 多次使用 `(some #(when pred %) coll)`: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 seq 函数定义,但你们都知道那些 ;): https://github.com/richhickey/clojure-contrib/blob/40b960bba41ba02811ef0e2c632d721eb199649f/src/main/clojure/clojure/contrib/seq.clj#L179
...