【问题标题】:Clojure Core function argument positions seem rather confusing. What's the logic behind it?Clojure Core 函数参数位置似乎相当混乱。其背后的逻辑是什么?
【发布时间】:2020-03-27 20:21:53
【问题描述】:

对于我作为一个新的 Clojurian 的我来说,当涉及到参数顺序/位置时,一些核心函数似乎相当违反直觉和令人困惑,这里有一个例子:

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

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

也许它背后有一些我还没有看到的规则/逻辑?

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

或者我应该直接记住它吗?

谢谢


有点题外话:

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

【问题讨论】:

  • 你可以在网上找到很多关于这个话题的讨论,尤其是在线程方面。例如hereThis here 是经典。

标签: clojure lisp clojurescript clojure-core


【解决方案1】:

Clojure.org 上有一个关于这个问题的常见问题解答:https://clojure.org/guides/faq#arg_order

核心函数中 arg 顺序的经验法则是什么?

主集合操作数排在第一位。这样就可以写 → 及其同类,它们的位置与它们是否具有可变的参数无关。在 OO 语言和 Common Lisp (slot-value, aref, elt) 中有这样的传统。

考虑序列的一种方法是从左侧读取,从右侧输入:

<- [1 2 3 4]

大多数序列函数消耗和产生序列。因此,一种将其可视化为链的方法:

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

考虑许多 seq 函数的一种方法是它们以某种方式被参数化:

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

因此,序列函数最后获取它们的源,以及它们之前的任何其他参数,并且部分允许如上所述的直接参数化。在函数式语言和 Lisps 中有这样的传统。

请注意,这与最后获取主操作数不同。一些序列函数有多个源(concat、interleave)。当序列函数是可变参数时,通常在它们的源中。

改编自comments by Rich Hickey

【讨论】:

    【解决方案2】:

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

    访问和“更改”单个元素将集合作为第一个元素:conj、assoc、get、update

    这样,您可以将(-&gt;&gt;) 宏与集合一致地使用, 以及一致地创建传感器。

    很少有人需要求助于(as-&gt;) 来更改参数顺序。如果您必须这样做,这可能是一个检查您自己的函数是否遵循该约定的机会。

    【讨论】:

    • 这大体上是正确的,但应该注意的是nth 系列函数打破了这个规则,首先采用序列,最后采用索引。我认为这是因为在 Clojure 确定自己的风格之前,它们的参数顺序是从其他 lisps 中具有相同名称的函数中借用的。有人争辩说nth 遵循这条规则,因为它首先需要一个集合,比如get,而不是一个序列,但我真的不买这个 - 你像序列一样使用它。
    • 好吧。有人会希望你在向量上使用 nth 或者你会支付线性访问成本。我不认为它违反了惯例。
    【解决方案3】:

    对于某些函数(尤其是“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 经常遵循“最大优先”或“最重要优先”的顺序(通常这些具有相同的结果)。因此,我们看到如下示例:

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

    这还表明,根据定义,任何可选值都必须位于最后(以便使用“rest”参数)。这在大多数语言(例如 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]))
    

    还有

    我个人觉得 真的很烦 尝试使用 partial 解析代码就像使用 evens-1 一样,尤其是对于用户定义函数的情况,甚至不像+那么简单的标准函数。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-08-14
      • 1970-01-01
      • 2020-09-08
      • 2021-03-30
      • 2015-08-12
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多