【发布时间】:2022-11-30 00:35:31
【问题描述】:
在 Clojure 中,每个变量都是不可变的。但是当我像下面的例子一样使用列表理解时,elem 变量似乎是可变的,因为每次 elem 被 1 覆盖,然后被 2 覆盖,然后被 3 覆盖,或者不是?
(for [elem [1 2 3]]
elem)
这是允许可变性的地方还是我遗漏了什么?
【问题讨论】:
标签: clojure
在 Clojure 中,每个变量都是不可变的。但是当我像下面的例子一样使用列表理解时,elem 变量似乎是可变的,因为每次 elem 被 1 覆盖,然后被 2 覆盖,然后被 3 覆盖,或者不是?
(for [elem [1 2 3]]
elem)
这是允许可变性的地方还是我遗漏了什么?
【问题讨论】:
标签: clojure
“突变”是指现有变量改变其内容。如果你有一个对变量的引用,你可以观察到这一点,查看它一次,将其值记为 X,然后再次查看同一个变量,注意它的值现在是 Y。这不是列表中发生的事情理解。
首先,让我们谈谈我希望你同意的一件事不是变异:使用不同的值多次调用一个函数。假设我们有
(defn triple [x]
(* x 3))
如果我们写[(triple 1) (triple 2)],我们是否说x发生了变异?当然不是。函数 triple 有两个不同的调用,每个调用都有不同的 x 值,但它们不是同一个变量:它们是 x 的不同实例。
列表理解是同一回事。主体是一个函数,它对每个输入进行一次评估。它没有看就像一个函数,因为没有 fn,但它确实是一个,无论是技术上(它宏扩展到 fn 的主体)还是哲学上(它处理输入的方式与上面的 triple 函数相同)。 (for [x xs] (f x)) 与写 (map f xs) 没有什么不同,后者不需要修改。
通常当新手担心 Clojure 中的突变时,他们担心的是let,它允许您替换现有的绑定:
(let [x 1
_ (prn x)
x 2]
(prn x))
这打印出1 2: 这不就证明x 发生了变异吗?不,它没有:旧的 x 仍然存在,它只是被隐藏了,所以你不能再引用它了。你可以通过使用一个函数来证明这一点,让你引用旧的x:
(let [x 1
f (fn [] x)
x 2]
(prn (f) x))
这仍然打印1 2,即使这两个打印都发生在x绑定到2之后。这是因为f仍然看到旧的x。新的x是一个无关的同名变量;您不妨将其命名为y 并重命名对它的所有引用。
【讨论】:
如 here 所述,值和身份之间的区别很重要。如果一个身份似乎发生了变化,那是因为它随着时间的推移与不同的状态值相关联。
【讨论】:
如果您有 C/C++ 背景,那么认为 elem 的行为类似于指向常量对象的非常量指针可能会有所帮助。
如果您有 Python/Java 背景,认为您正在遍历的集合中的值是不可变的可能会有所帮助。
无论如何,不变性问题是关于集合中元素的断言,而不是关于elem。
【讨论】:
elem 是指向 const 对象的 const 指针。
elem 在迭代期间绑定到不同的“对象”。
elem 变量,每个变量都永久固定为输入列表中的一个数字。
(let [fs (for [x [1 2 3]] (fn [y] (+ x y)))] (map (fn [f] (f 10)) (reverse fs)))。我们创建了一个包含 3 个闭包的列表,每个值 x 一个。然后我们以相反的顺序调用该列表中的函数,我们可以及时回顾为每个函数捕获的特定x。我希望您会同意,没有任何带有指向 const 对象的非常量指针的循环可以模仿这种行为。 (事实上,用 for 循环做这个是 Python 中的一个常见陷阱,因为它做使用单个变量!)。
elem 是可变的——我只是想提供一个第一方向。