Clojure 核心函数参数位置看起来相当混乱。其背后的逻辑是什么?

Clojure Core function argument positions seem rather confusing. What's the logic behind it?

对于我这个 Clojurian 新手来说,一些核心函数在参数方面似乎相当反直觉和令人困惑 order/position,这里有一个例子:

> (nthrest (range 10) 5) 
=> (5 6 7 8 9)

> (take-last 5 (range 10)) 
=> (5 6 7 8 9)

也许后面还有一些我还没有看到的rule/logic?

我不相信 Clojure 核心团队做出了如此多的出色技术决策,却忘记了函数 naming/argument 顺序的一致性。

还是我应该原封不动地记住它?

谢谢


有点跑题:

rand&rand-int VS random-sample - 函数命名似乎不一致的另一个例子,但这是一个很少使用的函数,所以没什么大不了的。

对于某些函数(尤其是 "seq in, seq out" 的函数),args 是有序的,因此可以按如下方式使用 partial

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest
  (let [dozen      (range 12)
        odds-1     (filterv odd? dozen)
        filter-odd (partial filterv odd?)
        odds-2     (filter-odd dozen) ]
    (is= odds-1 odds-2
      [1 3 5 7 9 11])))

对于其他函数,Clojure 通常遵循 "biggest-first" 或 "most-important-first" 的顺序(通常它们具有相同的结果)。因此,我们看到这样的例子:

(get <map> <key>)
(get <map> <key> <default-val>)

这也表明,根据定义,任何可选值都必须是最后一个(以便使用 "rest" args)。这在大多数语言中都很常见(例如 Java)。


郑重声明,我真的不喜欢使用部分函数,​​因为它们具有用户定义的名称(充其量)或内联使用(更常见)。考虑这段代码:

  (let [dozen   (range 12)
        odds    (filterv odd? dozen)

        evens-1 (mapv (partial + 1) odds)
        evens-2 (mapv #(+ 1 %) odds)
        add-1   (fn [arg] (+ 1 arg))
        evens-3 (mapv add-1 odds)]

    (is= evens-1 evens-2 evens-3
      [2 4 6 8 10 12]))

还有

我个人觉得真的很烦人 试图像 evens-1 一样使用 partial 解析代码,特别是对于用户定义函数的情况,甚至是不像 +.

这样简单的标准函数

如果 partial 与 2 个或更多参数一起使用,则尤其如此。

  • 对于 1-arg 的情况,evens-2 的函数文字对我来说更具可读性。

  • 如果存在 2 个或更多 args, 创建一个命名函数(本地函数,如所示evens-3),或常规 (defn some-fn ...) 全局函数。

使用 seqs 的函数通常将实际的 seq 作为最后一个参数。 (地图、过滤器、远程等)

访问和 "changing" 单个元素将集合作为第一个元素:conj、assoc、get、update

这样,您就可以在集合中始终使用 (->>) 宏, 以及始终如一地创建传感器。

很少有人不得不诉诸 (as->) 来更改参数顺序。如果您必须这样做,这可能是一个检查您自己的函数是否遵循该约定的机会。

Clojure.org 上有一个针对此问题的常见问题解答:https://clojure.org/guides/faq#arg_order

What are the rules of thumb for arg order in core functions?

Primary collection operands come first. That way one can write → and its ilk, and their position is independent of whether or not they have variable arity parameters. There is a tradition of this in OO languages and Common Lisp (slot-value, aref, elt).

One way to think about sequences is that they are read from the left, and fed from the right:

<- [1 2 3 4]

Most of the sequence functions consume and produce sequences. So one way to visualize that is as a chain:

map <- filter <- [1 2 3 4]

and one way to think about many of the seq functions is that they are parameterized in some way:

(map f) <- (filter pred) <- [1 2 3 4]

So, sequence functions take their source(s) last, and any other parameters before them, and partial allows for direct parameterization as above. There is a tradition of this in functional languages and Lisps.

Note that this is not the same as taking the primary operand last. Some sequence functions have more than one source (concat, interleave). When sequence functions are variadic, it is usually in their sources.

Adapted from comments by Rich Hickey.