为什么列表推导式中的变量不被视为 clojure 中的可变状态?

Dav*_*vid 1 clojure

在 Clojure 中,每个变量都是不可变的。但是当我像下面的例子一样使用列表理解时,变量elem似乎是可变的,因为每次都会elem被 1 覆盖,然后被 2 覆盖,然后被 3 覆盖,或者不是?

(for [elem [1 2 3]] 
  elem)
Run Code Online (Sandbox Code Playgroud)

这是允许可变性的一点还是我错过了一些东西?

ama*_*loy 9

“突变”是指现有变量改变其内容。如果您有一个变量的引用,查看它一次,将其值标记为 X,然后再次查看同一变量,标记其值现在为 Y,则可以观察到这一点。这不是列表中发生的情况理解。

首先,让我们谈谈我希望您同意的一件事不是突变:使用不同的值多次调用一个函数。假设我们有

(defn triple [x]
  (* x 3))
Run Code Online (Sandbox Code Playgroud)

如果我们写[(triple 1) (triple 2)],我们是否说它x已经变异了?当然不是。该函数有两次不同的调用triple,每次调用都有不同的值x,但它们不是同一个变量:它们是不同的实例化x

列表理解是同样的事情。主体是一个函数,针对每个输入计算一次。它看起来不像一个函数,因为没有fn,但它确实是一个函数,无论是在技术上(它宏展开到 a 的主体中fn)还是在哲学上(它以与上面的函数相同的方式处理输入triple)。(for [x xs] (f x))与写作没有什么不同(map f xs),不需要变异。

通常,当新手担心 Clojure 中的突变时,他们会担心let,它允许您替换现有的绑定:

(let [x 1
      _ (prn x)
      x 2]
  (prn x))
Run Code Online (Sandbox Code Playgroud)

打印出来1 2:这不就证明已经x变异了吗?不,事实并非如此:旧的x仍然存在,只是被遮蔽了,所以你不能再引用它了。您可以通过使用一个函数来证明这一点,让您引用旧的x

(let [x 1
      f (fn [] x)
      x 2]
  (prn (f) x))
Run Code Online (Sandbox Code Playgroud)

1 2即使两次打印都发生在x绑定到 2之后,它仍然会打印。这是因为f仍然看到旧的x. newx是一个不相关的同名变量;您不妨调用它y并重命名对它的所有引用。