【问题标题】:What determines when a collection is created?什么决定了何时创建集合?
【发布时间】:2012-05-09 01:33:18
【问题描述】:

如果我理解正确,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 以某种方式鼓励“函数式”编程方式,您不应该几乎总是返回集合吗?

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

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

首先我不知道如何转换 loop 使其产生不同于 nil 的东西,然后我尝试了以下方法:

(reduce println '(1 2 3))

但它打印的是 "1 2nil 3nil" 而不是我期待的 "1 2 3nil"

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

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

【问题讨论】:

  • 你对when的理解是错误的,你应该使用if。即使这样,您也会返回x,这是一个数字。实际上,您必须从某个地方创建一个列表才能返回一个。
  • @Seth Carnegie:好的,但是......我的问题是如何确定要创建什么:例如为什么 when 在这里不起作用,我怎么知道?这是否意味着 when 只能在有副作用的函数内部使用?
  • @CedricMartin 函数式编程没有魔法。返回一个数字就是这样做的——返回一个数字,仅此而已。 (在您的情况下,由于该数字返回到不关心返回值的上下文(非尾部位置的when body 子句),因此它会立即被丢弃,因此什么都不会发生。)如果你想要一个集合,你需要明确地创建它。关于when的使用:when正是为了的副作用,所以在学习函数式编程的时候,最好避免。
  • @MatthiasBenkard - 你为什么说'什么时候正好是为了副作用'?它不只是 if 的缩写形式,即。没有 else 子句的 if?
  • @sw1nn 它没有 else 子句,是的,但 if 也不需要 else 子句(它是可选的)。 when 的点是隐含的do,对于无副作用的代码是没有用的。

标签: collections clojure side-effects


【解决方案1】:

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

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

例如:

(take-while #(> % 1) (reductions (fn [s _] (/ s 2)) 128 (range)))

【讨论】:

  • 或者:(take-while #(> % 1) (iterate #(/ % 2) 128))
【解决方案2】:

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

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

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

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

在这种情况下,我将调用 printf 的位置替换为递归 一个将 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 将导致最终结果来自正在执行的二进制函数——在这种情况下,我们将序列中的数字减少为所有元素的总和。

你也可以提供一个初始值:

(reduce + 5 [1 2 3 4])                                                                                            

15  

执行以下操作:

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

HTH,

凯尔

【讨论】:

  • +1,这很有意义。 println 的副作用让我有点不知所措:我不明白为什么最后抛出的是 nil。但现在清楚多了。当然可能还有更多问题...... :)
猜你喜欢
  • 2016-02-16
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 2014-08-03
相关资源
最近更新 更多