【问题标题】:Passing Empty Lists to Functions to Collect Results将空列表传递给函数以收集结果
【发布时间】:2018-06-28 03:24:06
【问题描述】:

我一直无法找到任何关于我试图理解的内容的教义 Clojure 示例,并希望对此有所了解。

传统上,当我在命令式语言中使用递归时,我经常将一个空列表传递给递归函数,以便通过堆栈收集一些计算的结果。

作为 Java 中的一个小例子:

public List<Integer> results = new ArrayList<>();

private List<Integer> add(List<Integer> list, int count){
    if(count > 10){
        return list;
    }

    count = count + 1;
    list.add(count);

    return add(list, count);
}

如果运行:

add(results, 0)

将产生一个从 0 到 10 的值列表。

这是 Clojure 中的惯用语吗?

(defn t [list i]
    (conj list i)
    (if (<= i 10)
        (recur list (inc i))))

(t '() 0)

其次,为什么在我评价的时候:

(t '() 0)

我是否收到此错误:

clojure.lang.Compiler$CompilerException: java.lang.ClassCastException: java.lang.Long (in module: java.base) cannot be cast to clojure.lang.IFn

【问题讨论】:

  • conj 不会发生变异,因此您需要显式传递其返回值。即(defn t [list i] (if (&lt;= i 10) (recur (conj list i) (inc i)) list)) ...至于惯用语,我不是一个clojurist。这种风格在我看来更像方案。

标签: clojure


【解决方案1】:

您当然可以传入一个空列表作为结果的起点。这里适用的 clojure 成语是函数调用

(conj list i)

返回一个添加了 i 的新列表,但它不会改变列表,因此在 t 函数中,该行不会产生任何结果。要使用该函数调用的结果,应将其作为参数传递给类似于 (inc i) 的 recur

需要注意的第二点是if语句中没有else条件,所以一旦i的值大于10,if语句就会返回nil

(defn t1 [list i]
  (if (<= i 10)
    (recur (conj list i) (inc i))
    list))

【讨论】:

    【解决方案2】:

    这是 Clojure 中的惯用语吗?

    不是。

    在 Clojure 中,data structures are immutable。因此,一旦您调用传递列表的函数,该函数就无法修改它。所以,这不仅不是惯用的,而且是不可能的。

    相反,在 Clojure 中实现所需功能的惯用方法是调用递归函数来构建一个较小的列表,然后追加到其中。

    (defn my-range [n]
      (if (< n 0)
        []       ; empty list
        (conj
         (my-range (dec n)) ; recursive call to build smaller list
         n)))     
    

    话虽如此,您提供的Java的sn-p也不是很好;一般来说,这是一个很好的约定:

    • 如果方法具有 void 返回类型,您可能会认为它会产生副作用,并且可能会修改其参数之一
    • 如果方法有返回类型,那么您可能希望参数不会被修改。

    给方法一个返回类型同时修改一个参数会使代码混乱。您的代码的用户不知道是使用返回还是提供自己的要修改的列表。

    【讨论】:

      【解决方案3】:

      注意,conj 对于向量和列表类型的行为不同。对于向量,它会在末尾附加一个元素。对于一个列表,它把它放在它的前面:

      user=> (conj [1 2 3] 4)
      [1 2 3 4]
      user=> (conj '(1 2 3) 4)
      (4 1 2 3)
      

      因此,如果有人传递向量而不是列表,他或她将面临不可预知的行为。请注意,在 Clojure 中,向量比列表更常见(例如,与 Scheme 或 Common 列表不同)。

      concat 函数适用于两种类型:

      user=> (concat [1 2 3] [4])
      (1 2 3 4)
      user=> (concat '(1 2 3) [4])
      (1 2 3 4)
      

      然后,调用(conj list i) 会返回一个新列表,而不更改前一个列表。这个调用应该放在其他一些函数调用中,比如recurlet。否则,你就白费了。

      【讨论】:

        猜你喜欢
        • 2018-04-17
        • 1970-01-01
        • 2018-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-08
        相关资源
        最近更新 更多