将空列表传递给函数以收集结果

时间:2018-01-18 23:03:56

标签: clojure

我一直无法找到关于我想要了解的内容的任何教义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)无法强制转换为clojure.lang.IFn

3 个答案:

答案 0 :(得分:2)

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

(conj list i)

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

要注意的第二件事是if语句中没有其他条件,所以一旦i的值大于10,if语句将返回nil

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

答案 1 :(得分:1)

  

这是Clojure中的惯用语吗?

不是。

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

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

var my_obj, target_value;
    for (let obj_in_arr of my_arr) {
        if (obj_in_arr.property1 === target_value) {
            my_obj = obj_in_arr;
            break;
        }
    }

话虽如此,你提供的Java片段也不是很好;总的来说,这是一个很好的惯例:

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

为方法提供返回类型同时修改参数会导致代码混乱。您的代码的用户无法知道是使用退货还是提供自己要修改的列表。

答案 2 :(得分:0)

请注意,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内。否则,你徒劳无功。