【问题标题】:How does clojure bind variable parameters?clojure 如何绑定可变参数?
【发布时间】:2019-12-24 04:14:04
【问题描述】:

我是Clojure的新手,问题源于我曾经查看conj的源代码:

    (def conj 
      (fn ^:static conj
        ([] [])
        ([coll] coll)
        ([coll x] (clojure.lang.RT/conj coll x));4
        ([coll x & xs] ;1
         (if xs ;2
           (recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) ;3
           (clojure.lang.RT/conj coll x)))))

conj的源码显示它使用recur来实现该功能。这个源代码看起来很简单。 我感到困惑的是它在确定递归是否需要继续时使用的条件。看起来它检查变量参数是否为nil,但如果变量参数为nil,它很快就相当于conj 的第三个“arity”? 然后我尝试评估以下表达式:

user=> (conj [] 1 (next []))
[1 nil]
user=>

它工作正常并成功地将nil添加到向量中。我知道clojure实际上将nil包装在一个列表中并将其传递给函数,但我不明白为什么recur可以传递一个真正的nil?为什么clojure会识别并匹配正确的“arity”?


user=> (def my_conj
   (fn [coll x & xs]
     (println "xs is" xs)
     (if xs
       (recur (clojure.lang.RT/conj coll x) (first xs) (next xs))
       (clojure.lang.RT/conj coll x))))
#'user/my_conj
user=> (my_conj [] 1 (next []))
xs is (nil)
xs is nil
[1 nil]

【问题讨论】:

    标签: clojure clojure-java-interop clojure-core.logic


    【解决方案1】:

    好的,很抱歉之前没有意识到您遇到了我以前从未见过的 Clojure 行为的一个方面。今天我学到了一些关于 Clojure 的新东西,并且用了 10 年,这让我很惊讶。

    这在 Clojure 官方文档的几句话中提到了 recur,这里:https://clojure.org/reference/special_forms#recur

    这里是社区编写的示例和文档页面(因此不是“官方”),描述了这种带有可变参数的函数的递归行为:https://clojuredocs.org/clojure.core/recur#example-55ff3cd4e4b08e404b6c1c7f

    【讨论】:

      【解决方案2】:

      我建议复制此函数,将其名称更改为其他名称,并添加一些对您感兴趣的值的println 的调用,例如x、xs 等,并观察当您使用您感兴趣的不同参数调用函数时在 Clojure REPL 会话中打印的内容。

      如果xs 的值是除nilfalse 之外的任何值,则if xs 为真。值(nil) 是一个元素的列表,当用作if 条件表达式时被认为是真的。

      【讨论】:

      • 感谢您的回答,但实际上我在发布此问题之前尝试过这样做,结果与问题中描述的相同。
      • (next []) 计算结果为 nil,因此您的示例调用 (my_conj [] 1 (next []))) 与调用 (my_conj [] 1 nil) 相同。
      • 另外我不知道这是否令人困惑,但 clojure.lang.RT/conj 是对 Java 方法的调用,而不是递归调用。
      • (recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) ;3这不是递归调用吗?而且应该简化为:(recur result (first xs) nil),result(clojure.lang.RT/conj coll x)的求值结果,nil(next xs),因为xs 仅包含一个元素:nil
      • 还有一件事,我不能 100% 确定,但我相信这是真的:当您使用恰好 2 个参数调用 conj/my-conj 时,看起来 Clojure 可以调用任一版本用参数[coll x][coll x & xs] 声明,但在这种情况下,当从外部调用时,它似乎总是调用用[coll x] 声明的那个。然而,recur 调用永远不会在不同的声明之间切换——无论如何,它总是会调用具有相同数量的定义。我相信这是 Clojure 中 recur 的限制/属性。
      【解决方案3】:

      用参数[coll x & xs]声明的函数部分意思是“将第一个参数的值绑定到coll,将第二个参数的值绑定到x,然后如果没有更多的参数,将xs绑定为nil , 否则将 xs 与剩余参数列表的值绑定”。

      您可以通过这个完全不使用 recur 的更简单的函数看到这一点:

      user=> (defn my-fn [a & xs]
               (println "a=" a " xs=" xs))
      
      user=> (my-fn 1)
      a= 1  xs= nil
      
      user=> (my-fn 1 2)
      a= 1  xs= (2)
      

      【讨论】:

      • 但是如何评估这个表单:(recur (clojure.lang.RT/conj coll x) (first xs) (next xs)) ;3?不像你的例子,recur真的声明了额外的参数!如果你用这个例子来解释它,它可能最终被评估为(recur (clojure.lang.RT/conj coll x) (first xs)) ;3。因为最后一个参数是“nil”,所以不会传递。我认为解释这一点是有道理的,也许recur 做了什么,但我找不到其他信息来证明这一点..
      猜你喜欢
      • 1970-01-01
      • 2023-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      • 2012-07-26
      相关资源
      最近更新 更多