Idiomatic use case for set! in Clojure

时间:2017-06-09 12:49:52

标签: clojure

What are the idiomatic use-cases for set! in Clojure?

It's not easy to find uses! I grepped in some of the more popular Clojure OSS projects and there's almost none. The only uses I could find are for setting globals like warn-on-reflection, though it's unclear why these are not just set locally with binding.

Any suggestions?

2 个答案:

答案 0 :(得分:0)

您已回答了自己的问题。当您输入:

(def           foo 42)
(def ^:dynamic bar 43)

您正在创建一个全局变量。在大多数编程语言中,我们尝试最小化使用全局变量,因为它们会引入更多复杂性并且更难以理解代码(这会导致更多错误!)。当我们确实需要全局变量时,我们会尽量减少这些全局变量的变异。这就是您很少看到Clojure代码中使用的set!var-setvar-getalter-var-root的原因。

当Clojure代码确实需要改变全局状态时,binding通常是首选,因为当前线程离开var形式的范围时,有问题的binding返回到先前的状态。另一个好处是其他线程不受当前线程发生的任何binding突变的影响。

有几种方法可以在Clojure中操作全局变量。与动态 var相比,普通 var的答案会有所改变。以下是一些例子:

(ns tst.clj.core
  (:use clj.core
        tupelo.test)
  (:require
    [tupelo.core :as t] ))
(t/refer-tupelo)

(def            fred      0)
(def ^:dynamic *barney*   0) ; normally use "earmuffs" for dynamic vars
(def ^:dynamic  wilma     0) ; you aren't forced to use use them (but you should!)
(def            betty    29)

(dotest
  (throws?
    (binding [fred 1] ; can't bind non-dynamic var
      (is= fred 1)
      (throws? (set! fred 2))
      (is= fred 2)))

  (is= fred 0)
  (throws? (set! fred 2)) ;  can't set it
  (is= fred 0)
  (throws? (var-set #'fred 3)) ; java.lang.IllegalStateException: Can't change/establish root binding of: fred with set
  (is= fred 0)
  (is= (var-get #'fred) 0)

  (binding [*barney* 3]
    (is= *barney* 3)
    (set! *barney* 4) ; works fine
    (is= *barney* 4))
  (is= *barney* 3)  ; original value once leave binding scope

  (binding [wilma 6]
    (is= wilma 6)
    (set! wilma 7)  ; this works
    (is= wilma 7)
    (alter-var-root #'wilma inc)  ; this doesn't work!
    (is= wilma 7))
  (is= wilma 1)     ; the `inc` altered the root value, not the dynamic value in the `binding`
  (alter-var-root #'wilma #(+ 23 %)) ; can alter root value outside of binding
  (is= wilma 24)

  (is= betty 29)
  (alter-var-root #'betty inc) ; non-dynamic vars can have root value altered
  (is= betty 30)

)

答案 1 :(得分:0)

set!可以与线程本地binding结合使用,以允许函数跨调用堆栈进行通信。它通常是跨相互递归函数共享状态的便捷方式。

Clojure编译器是一个很好的例子 - 即使它是用Java实现的 - 使用dynamic Vars来跟踪内部状态。