在 Clojure 循环中重新定义 let'd 变量

发布于 2024-07-22 01:21:46 字数 470 浏览 6 评论 0原文

好的。 我一直在修改 Clojure,并且不断遇到同样的问题。 让我们看一下这段代码:

(let [x 128]
  (while (> x 1)
    (do
      (println x)
      (def x (/ x 2)))))

现在我希望它打印出一个以 128 开头的序列:相反

128
64
32
16
8
4
2

,它是一个无限循环,一遍又一遍地打印 128。 显然我预期的副作用没有发挥作用。

那么我该如何在这样的循环中重新定义 x 的值呢? 我意识到这可能不像 Lisp(也许我可以使用一个在其自身上递归的匿名函数),但如果我不知道如何设置这样的变量,我会发疯的。

我的另一个猜测是使用 set!,但这会给出“无效的分配目标”,因为我没有处于绑定形式。

请告诉我这是如何工作的。

OK. I've been tinkering with Clojure and I continually run into the same problem. Let's take this little fragment of code:

(let [x 128]
  (while (> x 1)
    (do
      (println x)
      (def x (/ x 2)))))

Now I expect this to print out a sequence starting with 128 as so:

128
64
32
16
8
4
2

Instead, it's an infinite loop, printing 128 over and over. Clearly my intended side effect isn't working.

So how am I supposed to redefine the value of x in a loop like this? I realize this may not be Lisp like (I could use an anonymous function that recurses on it's self, perhaps), but if I don't figure out how to set variable like this, I'm going to go mad.

My other guess would be to use set!, but that gives "Invalid assignment target", since I'm not in a binding form.

Please, enlighten me on how this is supposed to work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

撧情箌佬 2024-07-29 01:21:46

如果您认为在纯函数中拥有可变局部变量将是一个很好的方便功能,而且不会造成任何损害,因为该函数仍然保持纯函数,您可能会对这个邮件列表讨论感兴趣,其中 Rich Hickey 解释了他从语言中删除它们的原因。 为什么不使用可变局部变量?

相关部分:

如果局部变量是变量,即可变的,那么闭包可以关闭
可变状态,并且考虑到闭包可以逃逸(不需要一些额外的
禁止相同),结果将是线程不安全的。 还有人
肯定会这样做,例如基于闭包的伪对象。 结果
这将是 Clojure 方法中的一个巨大漏洞。

如果没有可变的局部变量,人们就被迫使用 recur,一种函数式的
循环构造。 虽然乍一看这可能看起来很奇怪,但它就像
简洁如带有突变的循环,结果模式可以是
在 Clojure 的其他地方重用,即 recur、reduce、alter、commut 等
都(逻辑上)非常相似。 尽管我可以检测到并且
防止变异闭包逃逸,我决定保持这种方式
为了一致性。 即使在最小的上下文中,非变异循环也是如此
比变异更容易理解和调试。 无论如何,瓦尔斯
可以在适当的时候使用。

后续帖子的大部分内容都涉及实现 with-local-vars 宏;)

If you think that having mutable local variables in pure functions would be a nice convenient feature that does no harm, because the function still remains pure, you might be interested in this mailing list discussion where Rich Hickey explains his reasons for removing them from the language. Why not mutable locals?

Relevant part:

If locals were variables, i.e. mutable, then closures could close over
mutable state, and, given that closures can escape (without some extra
prohibition on same), the result would be thread-unsafe. And people
would certainly do so, e.g. closure-based pseudo-objects. The result
would be a huge hole in Clojure's approach.

Without mutable locals, people are forced to use recur, a functional
looping construct. While this may seem strange at first, it is just as
succinct as loops with mutation, and the resulting patterns can be
reused elsewhere in Clojure, i.e. recur, reduce, alter, commute etc
are all (logically) very similar. Even though I could detect and
prevent mutating closures from escaping, I decided to keep it this way
for consistency. Even in the smallest context, non-mutating loops are
easier to understand and debug than mutating ones. In any case, Vars
are available for use when appropriate.

The majority of the subsequent posts concerns implementing a with-local-vars macro ;)

放肆 2024-07-29 01:21:46

def 定义了一个顶级变量,即使您在函数或某些代码的内部循环中使用它。 你在 let 中得到的不是变量。 根据 let 的文档:

用let创建的局部变量不是变量。 一旦创建,他们的价值观就永远不会改变!

(强调不是我的。)这里的示例不需要可变状态; 您可以使用looprecur

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

如果你想变得更奇特,你可以完全避免显式的循环

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
  (doseq [x xs] (println x)))

如果您确实想要使用可变状态,atom 可能会起作用。

(let [x (atom 128)]
  (while (> @x 1)
    (println @x)
    (swap! x #(/ %1 2))))

(您不需要 dowhile 会为您将其主体包装在一个显式的主体中。)如果您真的非常想要这样做对于 vars 你必须做这样可怕的事情。

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))

但这非常丑陋,而且根本不是 Clojure 惯用的。 为了有效地使用 Clojure,您应该尝试停止考虑可变状态。 尝试以非函数式风格编写 Clojure 代码肯定会让您发疯。 一段时间后,您可能会惊喜地发现实际上很少需要可变变量。

def defines a toplevel var, even if you use it in a function or inner loop of some code. What you get in let are not vars. Per the documentation for let:

Locals created with let are not variables. Once created their values never change!

(Emphasis not mine.) You don't need mutable state for your example here; you could use loop and recur.

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

If you wanted to be fancy you could avoid the explicit loop entirely.

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
  (doseq [x xs] (println x)))

If you really wanted to use mutable state, an atom might work.

(let [x (atom 128)]
  (while (> @x 1)
    (println @x)
    (swap! x #(/ %1 2))))

(You don't need a do; while wraps its body in an explicit one for you.) If you really, really wanted to do this with vars you'd have to do something horrible like this.

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))

But this is very ugly and it's not idiomatic Clojure at all. To use Clojure effectively you should try to stop thinking in terms of mutable state. It will definitely drive you crazy trying to write Clojure code in a non-functional style. After a while you may find it to be a pleasant surprise how seldom you actually need mutable variables.

北渚 2024-07-29 01:21:46

变量(这就是当你“定义”某些东西时得到的)并不意味着被重新分配(但可以):

user=> (def k 1)
#'user/k
user=> k
1

没有什么可以阻止你这样做:

user=> (def k 2)
#'user/k
user=> k
2

如果你想要一个线程本地可设置的“位置”,你可以使用“绑定”和“设置!”:

user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0

那么你可以写一个像这样的循环:

user=> (binding [j 0]
         (while (< j 10)
           (println j)
           (set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil

但我认为这是非常不惯用的。

Vars (that's what you get when you "def" something) aren't meant to be reassigned (but can be):

user=> (def k 1)
#'user/k
user=> k
1

There's nothing stopping you from doing:

user=> (def k 2)
#'user/k
user=> k
2

If you want a thread-local settable "place" you can use "binding" and "set!":

user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0

So then you can write a loop like this:

user=> (binding [j 0]
         (while (< j 10)
           (println j)
           (set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil

But I think this is pretty unidiomatic.

找回味觉 2024-07-29 01:21:46

您可以更惯用地使用 iteratetake-while 来代替,

user> (->> 128
           (iterate #(/ % 2))
           (take-while (partial < 1)))

(128 64 32 16 8 4 2)
user>

You could more idiomatically use iterate and take-while instead,

user> (->> 128
           (iterate #(/ % 2))
           (take-while (partial < 1)))

(128 64 32 16 8 4 2)
user>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文