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 中都有looplet 绑定将在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 设置为以initcoll 的第一项作为参数调用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宏:从地图创建局部变量[重复]

Clojure - recur 适用于循环或 let 语句?

forin循环的变量如何变为全局

关于var let const ~

clojure for function resetts let

在clojure中让vs def