2017-05-24 82 views
0

我已經寫了幾個Clojure程序,但我幾乎不記得我使用了閉包。何時使用Clojure的關閉?

在Clojure中使用閉包的最佳用例是什麼?

另外,您是否可以提供對初學者有用的用例和例子。

+3

我真的不認爲這與Clojure有很大關係。無論語言如何,我希望關閉的用例都是相同的。 – Carcigenicate

+0

@Carcigenicate公平,當然,以一種已知的語言來請求示例。標籤顯示OP意識到這個問題更廣泛地適用。沒有「Clojure」,標題會更好。 – Thumbnail

+0

當然,但我想讓那些不熟悉閉包的Clojure初學者更容易理解這個概念。 –

回答

3

這是一個非常簡單的例子,但是我使用閉包來「部分應用」函數,如果我已經有1個參數,但是需要稍後再獲取另一個參數。你可以解決,通過做這樣的事情在函數內部:

(let [x 10 
     f #(- % x)] 
    f) 

我用他們所有的時間,但它們的使用是如此簡單,很難認爲是不做作的例子。

我在Lorenz Systems最近的一個項目中發現了這個問題。 Hue-f是一個函數,當給定Lorenz系統的x,y和z座標時,返回一定的色調來爲該線條的該部分着色。它形成了隨機生成一個封閉:

(def rand-gen (g/new-rand-gen 99)) 
(def hue-f #(co/test1 % %2 %3 (g/random-double 1 1.05 rand-gen) 1.7)) 
                 ;^Closure 

雖然在這種情況下,關閉是不必要的,因爲rand-gen是全球性的反正。如果我想「讓必要的」,我可以改變它的東西,如:

(def hue-f 
    (let [rand-gen (g/new-rand-gen 99)] 
    #(co/test1 % %2 %3 (g/random-double 1 1.05 rand-gen) 1.7))) 
              ;^Closure 

(我覺得我得到了括號那裏的Lisp是很難寫的「自由之手」

+2

Upvoting,因爲我只是喜歡舊的讓lambda。 – yorodm

1

閉包允許你捕獲某些狀態以備後用,即使在創建狀態的上下文不可觸及的情況下,它幾乎就像是一個「門戶」:你通過門戶伸手,你可以抓住在某些不屬於你當前的領域

因此,最有用的封閉案例是當你需要控制或訪問某些東西,但沒有原始的處理。

一個用例如下:比方說,我想創建一個自行完成某項工作的線程,但我希望能夠「通話」該線程並說,「您的工作已經完成,請停止你正在做的事情。「

(defn do-something-forever 
    [] 
    (let [keep-going (atom true) 
     stop-fn #(reset! keep-going false)] 
    (future (while @keep-going 
       (println "Doing something!") 
       (Thread/sleep 500)) 
      (println "Stopping...")) 
    stop-fn)) 

有幾種方法來完成此(顯式地創建一個線程並中斷它,例如),但此方法返回一個函數,它,進行評價時,修改所述封閉變量keep-going使得的值在完全不同的上下文中運行的線程會看到更改並停止循環。

就好像我伸手通過門戶並翻轉開關;沒有關閉,我沒有辦法抓住keep-going

1

閉合,捲曲和部分應用在語言的可組合性中起着重要作用。在設計上,它提供了一定程度的靈活性,以構建適應預期結果的功能,這些功能不是靜態定義的。

術語閉包來自於能夠「關閉」一個範圍內的變量,並將閉合的超過變量的值超出它所聲明的範圍。

閉包可以像任何其他數據/函數引用一樣傳遞。

而且,與函數不同,閉包在聲明時不進行評估。我忘記了這個lambda微積分術語,但它是推遲結果生成的一種方式。

可以聲明像匿名函數的封閉(使用的(fn []...)#(...)速記的一個不同之處在於區別於拉姆達是如果它關閉在通過更高級別的範圍通過在變量中。

1

一個很好的例子採用封閉是當你從一個函數返回一個函數內部函數仍然「記住」這是附近創建的變量

例:

(defn get-caller [param1 param2] 
    (fn [param3] 
    (call-remote-service param1 param2 param3))) 

(def caller (get-caller :foo 42)) 
(def result (caller "hello")) 

更好的(和真實的)示例可能是廣泛用於HTTP處理的中間件模式。假設您想要爲每個HTTP請求分配一個用戶。這裏是一箇中間件:

(defn auth-middleware [handler] 
    (fn [request] ;; inner function 
    (let [user-id (some-> request :session :user_id) 
      user (get-user-by-id user-id)] 
     (handler (assoc request :user user))))) 

A handler參數是一個HTTP處理程序。現在,每個請求都會有一個:user字段,填充用戶數據或nil值。

現在你包與中間件(的Compojure語法)的處理程序:

(GET "/api" request ((auth-middleware your-api-handler) request)) 

因爲你的內部函數引用handler變量,您使用的是關閉。

0

你從來沒有寫過map的函數或filter的一個函數來描述環境嗎?

(let [adults (filter #(>= (:age %) 21) people)] ...) 

...或者,給謂詞名稱:

(def adult? #(>= (:age %) 21)) 

這將關閉在不斷21。它可能來自國家或活動領域(婚姻,刑事責任,投票權)或其他任何領域。

0

除了其他答案,緩存通常是使用閉包的好地方。如果你能負擔得起的內存緩存,最簡單的方法之一是將緩存在封閉:

(defn cached-get-maker [] 
    (let [cache (volatile nil)] 
    (fn [params] 
     (if @cache 
     @cache 
     (let [result (get-data params)] 
      (vreset! cache result) 
      result))))) 

根據執行的情況下,你可以使用原子代替揮發性的。 Memoizing也是封閉式的,是一種專門的緩存技術。只是你不需要自己實現它,Closure提供memoize

+0

對不起,不知道如何在手機上縮進代碼。 – DjebbZ