1

如果我理解正确,Clojure 可以返回列表(就像在其他 Lisps 中一样),也可以返回向量和集合。

我真正不明白的是为什么并不总是有一个集合被返回。

例如,如果我采用以下代码:

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

它确实打印了 128 64 32 16 8 4 2。但这只是因为println被调用并且println具有打印某些东西的副作用(?)。

所以我尝试用这个替换它(删除println):

(loop [x 128]
  (when (> x 1)
    x
    (recur (/ x 2))))

我期待得到一些收集(据说是一个列表),像这样:

(128 64 32 16 8 4 2)

但相反,我得到了nil

我不明白哪个决定了什么创建一个集合,什么没有,以及你如何从一个切换到另一个。此外,看到 Clojure 以某种方式鼓励了“函数式”编程方式,您不应该几乎总是返回集合吗?

为什么有这么多显然不返回任何集合的函数?制作这些退货系列的惯用方式是什么?

例如,我将如何通过首先构造一个集合然后以一种惯用的方式迭代(?)而不是生成的列表/向量来解决上述问题?

首先,我不知道如何转换循环以使其产生除nil以外的其他内容,然后我尝试了以下操作:

(reduce println '(1 2 3))

但它打印“1 2nil 3nil”而不是我期待的“1 2 3nil” 。

我意识到这是基本的东西,但我才刚刚开始,我显然在这里缺少基本的东西。

(PS:适当地重新标记,我不知道我应该在这里使用哪些术语)

4

2 回答 2

3

其他一些评论指出,when 不像 if 那样真正起作用 - 但我不认为这真的是你的问题。

loop 和 recur 形式创建了一个迭代 - 就像其他语言中的 for 循环一样。在这种情况下,当您打印时,确实只是为了副作用。如果你想返回一个序列,那么你需要构建一个:

(loop [x 128                                                                                                        
       acc []]                                                                                                      
  (if (< x 1)                                                                                                       
    acc                                                                                                             
    (recur (/ x 2)                                                                                                  
           (cons x acc))))                                                                                          

=> (1 2 4 8 16 32 64 128)

在这种情况下,我将调用 printf 的位置替换为 recur将 x 添加到该累加器前面的表单。在 x 小于 1 的情况下,代码返回累加器 - 因此是一个序列。如果要添加到向量的末尾而不是前面,请将其更改为 conj:

(loop [x 128                                                                                                    
       acc []]                                                                                                  
  (if (< x 1)                                                                                                   
    acc                                                                                                         
    (recur (/ x 2)                                                                                              
           (conj acc x))))                                                                                      

=> [128 64 32 16 8 4 2 1] 

你得到 nil 因为那是你的表达式的结果——最终 println 返回的结果。

这一切有意义吗?

reduce 并不完全相同——它用于通过重复将二进制函数(一个接受 2 个参数的函数)应用于初始值和序列的第一个元素,或序列的前两个元素来减少列表第一次迭代的序列,然后后续迭代传递上一次迭代的结果和序列中的下一个值。一些示例可能会有所帮助:

(reduce + [1 2 3 4])                                                                                            

10  

这将执行以下操作:

(+ 1 2) => 3
(+ 3 3) => 6
(+ 6 4) => 10

归约将导致最终结果来自正在执行的二进制函数——在这种情况下,我们将序列中的数字归约为所有元素的总和。

您还可以提供初始值:

(reduce + 5 [1 2 3 4])                                                                                            

15  

它执行以下操作:

(+ 5 1)  => 6
(+ 6 2)  => 8
(+ 8 3)  => 11
(+ 11 4) => 15

高温下,

凯尔

于 2012-04-28T18:53:44.087 回答
2

对集合的广义抽象在 Clojure 中称为序列,许多数据结构都实现了这种抽象,因此您可以在这些数据结构上使用所有与序列相关的操作,而无需考虑将哪个数据结构传递给您的函数。

就示例代码而言——循环,recur 用于递归——所以基本上任何你想用递归解决的问题都可以用它来解决,经典的例子是阶乘。虽然您可以使用循环创建向量/列表 - 通过将累加器用作向量并继续向其附加项目并在递归返回累积向量的存在条件下 - 但您可以使用reductionstake-while函数来执行此操作,如下所示。这将返回一个惰性序列。

前任:

(take-while #(> % 1) (reductions (fn [s _] (/ s 2)) 128 (range)))
于 2012-04-29T05:40:39.410 回答