5

我注意到一些库,如 clojure-twitter 使用特殊的变量(用于动态绑定的变量,用星号包围)进行 oauth 身份验证。您将身份验证保存在 var 中,然后使用 (with-oauth myauth ..)。我认为这是解决此类问题的一个非常好的解决方案,因为您可以为应用程序的每个用户重新绑定auth var。

我在我一直在写的电子邮件客户端中采用了类似的方法。我有一个名为session的特殊 var ,我用当前用户的会话和用户信息绑定到一个映射,并且有各种重要的函数使用来自该 var 的信息。我写了一个宏,with-session,在传递给 with-session 的一组表单的上下文中临时重新绑定它。事实证明这是一个非常干净的解决方案(对我来说)。

所以,我的问题是这样的:我是在“按部就班”吗?这是一个糟糕的设计决定,还是特殊变量的预期用途之一?

4

3 回答 3

7

你似乎做得完全正确。事实上,有许多内置的 / contrib 宏的工作方式类似,比如with-out-stror clojure.contrib.sql/with-connection。后者是当今 Clojure 基础架构中相当关键的部分,因此无论它使用什么成语,都已被很多人仔细研究过。

要记住的重要问题是,在bindings/with-bindings表单范围内启动的线程不会继承相关变量的反弹值;相反,他们看到的是根绑定。如果您想将绑定传播到工作线程/代理,请显式传递它们(例如作为函数参数)或使用bound-fn.

于 2010-02-15T04:10:41.753 回答
3

每次您创建一个计划重新绑定的全局变量时,您都会为访问该变量的每个函数添加一个额外的隐式参数。与正确(显式)参数不同,此隐藏参数不会出现在函数的签名中,并且可能几乎没有迹象表明该函数正在使用它。你的代码变得不那么“实用”了;根据这些全局动态变量的当前状态,使用相同的参数调用相同的函数可能会导致不同的返回值。

全局变量的好处是你可以很容易地指定一个默认值,它可以让你变得懒惰,不必将这个变量传递给每个使用它的函数。

缺点是您的代码更难阅读、测试、使用和调试。而且您的代码可能更容易出错;在调用使用它的函数之前,很容易忘记绑定或重新绑定 var,但是session当参数在 arglist 中时,忘记传递参数就不是那么容易了。

所以你最终会遇到神秘的错误,以及函数之间奇怪的隐含依赖关系。考虑这种情况:

user> (defn foo [] (when-not (:logged-in *session*) (throw (Exception. "Access denied!"))))
#'user/foo
user> (defn bar [] (foo))
#'user/bar
user> (defn quux [] (bar))
#'user/quux
user> (quux)
; Evaluation aborted.  ;; Access denied!

的行为quux隐含地取决于具有值的会话,但是除非您深入研究每个函数quux调用以及这些函数调用的每个函数,否则您不会知道这一点。想象一个 10 或 20 层深的调用链,底部有一个函数,取决于*session*. 玩得开心调试。

相反,如果您有(defn foo [session] ...), (defn bar [session] ...), (defn quux [session] ...),那么您会立即明白,如果您调用quux,则最好准备好会话。

就个人而言,我会使用显式参数,除非我有一个强大的、健全的默认值,大量函数使用,我计划很少或永远不会重新绑定。(例如,将 STDOUT 作为显式参数传递给每个想要打印任何内容的函数是很愚蠢的。)

于 2010-02-15T20:07:13.933 回答
1

绑定函数非常适合测试代码。

我在我的测试代码中广泛使用重新绑定包装函数来做一些事情,比如模拟随机数生成器,使用固定的块大小等......这样我就可以根据已知输出实际测试加密函数。

(defmacro with-fake-prng [ & exprs ]
  "replaces the prng with one that produces consisten results"
  `(binding [com.cryptovide.split/get-prng (fn [] (cycle [1 2 3]))
             com.cryptovide.modmath/mody 719
             com.cryptovide.modmath/field-size 10]
        ~@exprs))

(is (= (with-fake-prng (encrypt-string "asdf")) [23 54 13 63]))  

When using bindings its useful to remember that they only rebind for the current thread so when you fire something off in pmap which uses the thread pool you may loose your bindings. If you have some code that builds a string in parallel like this:

(with-out-str
    (pmap process-data input))

Using that innocent looping \p in front of the map will cause the binding to go away because it will run the process-data function in several threads from the thread-pool.

EDIT: Michał Marczyk points out the bound-fn宏,您可以使用它来防止在使用线程时丢失绑定。

于 2010-02-16T18:09:46.723 回答