let 内的 Clojure 循环(全局 v 局部变量)
Posted
技术标签:
【中文标题】let 内的 Clojure 循环(全局 v 局部变量)【英文标题】:Clojure loop inside let (global v local variable) 【发布时间】:2016-05-14 20:01:08 【问题描述】:我正在编写与 clojure 中的“reduce”功能相同的代码 ex) (reduce + [1 2 3 4]) = (+ (+ (+ 1 2) 3) 4)。
(defn new-reduce [fn coll]
(def answer (get coll 0))
(loop [i 1]
(when (< i (count coll))
(def answer (fn answer (get coll i)))
(recur (inc i))))
answer)
在我的代码中,我使用了全局变量,对我来说,这种方式更容易理解。显然,人们说最好将全局变量更改为局部变量,例如 let。所以我尝试了..
(defn new-reduce [fn coll]
(let [answer (get coll 0)]
(loop [i 1]
(when (< i (count coll))
(fn answer (get coll i))
(recur (inc i))))))
说实话,我对 let 函数并不是很熟悉,即使我尝试了非常简单的代码,它也不起作用。有人可以帮我修复这段代码并帮助我理解 let (局部变量)是如何工作的吗?谢谢你。 (ps.在 let 函数中有循环的非常简单的代码也很棒)。
【问题讨论】:
任何时候您在let
中都有loop
,let
绑定将在loop
的所有迭代中保持不变。请记住,let
绑定是绑定,而不是变量。
【参考方案1】:
Let 不会创建本地“变量”,它会为值命名,并且不会让您在命名后更改它们。所以引入 let 更像是定义一个局部常量。
首先,我将在loop
表达式中添加另一个项目来存储目前的值。每次通过循环,我们都会更新它以包含新信息。这种模式很常见。我还需要在函数中添加一个新参数来保持初始状态(reduce 作为一个概念需要这个)
user> (defn new-reduce [function initial-value coll]
(loop [i 0
answer-so-far initial-value]
(if (< i (count coll))
(recur (inc i) (function answer-so-far (get coll i)))
answer-so-far)))
user> (new-reduce + 0 [1 2 3])
6
这会将“全局变量”移动到循环表达式的本地名称中,每次循环在您跳回顶部时可以更新一次。一旦它到达循环的末尾,它将返回答案作为函数的返回值,而不是再次重复。构建自己的 reduce 函数是了解如何有效使用 reduce 的好方法。
有一个函数可以引入真正的局部变量,尽管它几乎从未在 Clojure 代码中使用过。它仅在运行时引导代码中真正使用。如果您真的很好奇,请仔细阅读绑定。
【讨论】:
Arthur,使用fn
以外的参数名称会更好吗,因为那是宏的名称? OP 使用它,但她/他正在学习。 (fn answer-so-far ...)
看起来像一个语法错误的函数定义,即它应该是 (fn [answer-so-far] ...)
或其他东西。这在上下文中没有意义,但是使用fn
作为参数让我陷入了循环……一会儿。
是的,没错。在向 D.K. 的例子展示关闭的东西时,我并没有指出这很奇怪(这让我对答案的初稿感到困惑)我将其改为“功能”【参考方案2】:
这是一个复制标准reduce
行为的简单实用解决方案:
(defn reduce
([f [head & tail :as coll]]
(if (empty? coll)
(f)
(reduce f head tail)))
([f init [head & tail :as coll]]
(cond
(reduced? init) @init
(empty? coll) init
:else (recur f (f init head) tail))))
这里没有loop
,因为函数本身就是递归点。我个人觉得递归地考虑这个问题更容易,但由于我们使用 recur
的尾递归,你也可以命令式/迭代地考虑它:
-
如果
init
是提前返回的信号,则返回其值,否则转至步骤2
如果coll
为空则返回init
,否则转步骤3
将init
设置为以init
和coll
的第一项作为参数调用f
的结果
将coll
设置为coll
中除第一个之外的所有项目的序列
转到步骤 1
实际上,在幕后(通过尾调用优化等),这就是真正发生的事情。我鼓励您比较同一解决方案的这两个表达式,以更好地了解如何在 Clojure 中解决这类问题。
【讨论】:
或许更清楚的是(if (or (reduced? init) (empty? coll)) init (recur f (f init head) tail))
。
@Thumbnail 不,这不正确(尝试一些示例here)。您需要取消引用 Reduced
对象。
@Thumbnail 别担心 :)以上是关于let 内的 Clojure 循环(全局 v 局部变量)的主要内容,如果未能解决你的问题,请参考以下文章
Clojure - recur 适用于循环或 let 语句?