2011-08-24 34 views
11

我知道binding表單允許在clojure中重新綁定動態範圍。到目前爲止,我見過的唯一用途是用於I/O,比如print,其中*out*可以反彈到您當時想要的任何作家。在clojure中使用'綁定'有什麼好的例子?

我希望看到一些真正利用binding功能的例子,其中的其他設施真的不起作用。就我個人而言,我只在使用用戶提供的對象到所有函數時非常乏味的情況下才使用它。基本上是我嘗試創建輔助函數使用的上下文的情況。 (類似於這種情況When should one use the temporarily-rebind-a-special-var idiom in Clojure?)更具體地說,我是依靠用戶創建一個動態綁定到var的允許數據庫函數來知道要操作什麼。當用戶需要編寫大量嵌套的數據庫函數調用時,這是特別有用的。通常情況下,如果我需要編寫宏來讓自己更容易,但是要求用戶這樣做似乎很糟糕,那麼我確定。話雖如此,我儘量避免這麼做。

什麼是'綁定',我可以複製並納入我的代碼的一些其他良好用例?

+0

給大家,到目前爲止都是很好的答案。如果有其他人想要刺傷,我會在選擇一個答案之前多留幾天。 – bmillare

回答

8

我使用綁定有兩個原因:

  1. 使用「全局」的資源,例如數據庫連接或消息中介通道運行重寫常量或其他符號的其它值測試

testing

我工作在一個分佈式系統上有幾個組件通過消息交換髮送消息進行通信。這些交換機具有全局名稱,這是我喜歡這樣的定義:

(ns const) 
(def JOB-EXCHANGE "my.job.xchg") 
(def CRUNCH-EXCHANGE "my.crunch.xchg") 
;; ... more constants 

這些常量在許多地方被用來將消息發送到正確的地方。爲了測試我的代碼,我的測試套件的一部分運行使用實際消息交換的代碼。但是,我不希望我的測試干擾實際系統。

爲了解決這個問題,我總結我的測試代碼的binding呼叫覆蓋這些常量:

;; in my testing code: 
(binding [const/CRUNCH-EXCHANGE (str const/CRUNCH-EXCHANGE (gensym "-TEST-")) 
      const/CRUNCH-TASK-QUEUE (str const/CRUNCH-TASK-QUEUE (gensym "-TEST-"))] 
    ;; tests here 
) 

這裏面binding功能,我可以調用使用常數的任何代碼,它會使用重寫的值。

使用全球資源

另一種方式我使用綁定是「修復」特定範圍內的全球或單資源的價值。這裏有一個RabbitMQ的庫我寫的,我在那裏一個RabbitMQ的Connection的值綁定到符號*amqp-connection*的例子讓我的代碼可以使用它:

(with-connection (make-connection opts) 
    ;; code that uses a RabbitMQ connection 
) 

with-connection實現相當簡單:

(def ^{:dynamic true} *amqp-connection* nil) 

(defmacro with-connection 
    "Binds connection to a value you can retrieve 
    with (current-connection) within body." 
    [conn & body] 
    `(binding [*amqp-connection* ~conn] 
    [email protected])) 

我的RabbitMQ庫中的任何代碼都可以使用*amqp-connection*中的連接,並假定它是有效的,打開Connection。或者使用(current-connection)功能,它拋出,當你忘記在with-connection來包裝你的RabbitMQ調用一個描述異常:

(defn current-connection 
    "If used within (with-connection conn ...), 
    returns the currently bound connection." 
    [] 
    (if (current-connection?) 
    *amqp-connection* 
    (throw (RuntimeException. 
     "No current connection. Use (with-connection conn ...) to bind a connection.")))) 
+0

「(if(current-connection?)」表達式在最後一個代碼塊中是否正確?它可以(或者應該是)「if(* amqp-connection *)」而不是「current-connection?」 '(true?* amqp-connection *)'或'(some?* amqp-connection *)'? –

+1

Kenny,'current-connection?'只存在'var'* amqp-connection *' 。它的實現可以像'(true?* amqp-connection *)'一樣簡單,這是我想的代碼風格問題,直接使用var沒有任何壞處。 – Gert

2

在VimClojure後端你可能在同一個JVM上運行幾個repls。但是由於Vim和後端之間的連接不是連續的,你可能會爲每個命令獲得一個新的線程。所以你不能輕易保留命令之間的狀態。

VimClojure做了什麼,如下。它設置了一個binding,其中包含所有有趣的變量,如*warn-on-reflection**1*2等等。然後它執行命令,然後在一些醒目的基礎設施中存儲binding中可能發生變化的變量。

所以每個命令只是說「我屬於repl 4711」,它會看到所述repl的狀態。在不影響repl 0815狀態的情況下。

2

綁定功能在測試代碼中確實有幫助。這是在vars中存儲函數的最大優點之一(就像Clojure默認的那樣)。

摘自我寫的密碼程序。

(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]))] 
    [email protected])) 

您如何測試密鑰生成器函數?它應該是不可預測的。你可以在任何地方線程(if testing ...)或使用某種模擬框架。或者你可以使用一個「動態嘲諷」隨機數發生器的宏,並將這個僅放在測試代碼中,讓你的生產環境免於被竊。

(deftest test-key-gen 
    (with-fake-prng 
     ....)) 
相關問題