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

欢迎!请查看关于页面,了解更多关于该功能的信息。

+39

我通常会写:(some #(when (pred %) %) ...),并且经常错误地将其写为(some #(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。

总的使用: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)))
请注意,我不想基于过滤器来实现 first-by,因为分块

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

差异巨大

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

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

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

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

就不会引起任何分块。事实上,它甚至没有意识到序列的存在。
啊,(completing reduced),太棒了!比基于 xforms 提出的方案更简洁。
抱歉,我是指 `(completing (fn [_ x] (reduced x)))`。已修复。
+2

FWIW,之前已经拒绝过类似的提案:[https://clojure.atlassian.net/browse/CLJ-2056](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`,因为另一种方式是使用`first`包装`filter`。
0

已编辑

正如另一个答案中提到的,这已经在过去被考虑并拒绝了。我认为从那时起没有什么变化。

关于这个的轶事并不是特别有用,但数据是。如果你可以使用grep.app、grasp或其他软件来收集使用数据,这将是有帮助的。同时,在常见实用库中的现有类似函数及其使用(如果它们有所不同的话)的列表也是有用的。

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

...