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

欢迎!请在关于页面上了解更多有关该方式的信息。

+39
Clojure
编辑

我经常写作:(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。

总共使用了 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)))
请注意,我不想基于过滤操作来实现 first-by,因为分块处理

https://twitter.com/borkdude/status/1567525617549152257
(first xform coll) 由于它会使用过滤操作返回的算子参数,因此不会受到分块处理的影响。

巨大的差异

(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](https://clojure.atlassian.net/browse/CLJ-2056)

Alex的回答很有趣,我也同意他的观点。

不过,尽管我同意,在处理大数据集时线性搜索确实效率低下,但我的这个操作最常用的场景是处理短序列,例如在处理文本行时。为单次查找建立索引结构仍然需要线性处理数据,对于短序列来说效率也远低于数组映射等。

如果另一种结构总是正确的途径,那么我们就不会有十几个人在的这个票据上立即投票,我们中的许多人都会评论说我们经常这样做。
+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 等工具收集使用数据,将会有所帮助。另外,保留现有类似功能的通用库列表及其用法(以及如果有的话,它们实施的差异)也很有用。

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

...