2013-10-30 70 views
2
Java實例結合

我一個實例綁定到一個VAR:正確的Clojure到瓦爾

(ns org.jb 
    (:import (java.awt PopupMenu 
        TrayIcon 
        Toolkit 
        SystemTray) 

      (javax.swing JFrame 
         Action))) 

(def ^:dynamic popupmenu) 
(def ^:dynamic image) 
(def ^:dynamic trayicon) 
(def ^:dynamic tray) 

(defn start-app [appname icon] 
    (binding [popupmenu (new PopupMenu) 
      image (.. Toolkit (getDefaultToolkit) (getImage icon)) 
      trayicon (new TrayIcon image appname popupmenu) 
      tray (. SystemTray getSystemTray)] 

    (. trayicon setImageAutoSize true)  

    (. tray add trayicon))) 

(start-app "escap" "res/escap_icon.png") 

錯誤:

ClassCastException clojure.lang.Var$Unbound cannot be cast to java.awt.Image org.jb/start-app (org\jb.clj:17) 

我預先定義的無功與

(def image) 

甚至試過

(def ^:dynamic image) 

無法理解消息的預期內容。

但是,使用let代替詞法範圍內的綁定工作。但是要實現動態綁定。

回答

7

我在這裏看到的是一個空的binding窗體,沒有代碼。一旦您離開binding表單,變量綁定就會超出範圍。根據您的錯誤消息,它看起來像您試圖使用綁定表單外的image var。您需要確保所有使用image的代碼都放置在綁定中。

所以,與其這樣:

(binding [*image* (.. Toolkit (getDefaultToolkit) (getImage "icon.png"))]) 
(display-image *image*) 

這樣做:

(binding [*image* (.. Toolkit (getDefaultToolkit) (getImage "icon.png"))] 
    (display-image *image*)) 

另一個可能的問題是,綁定表達式是並行評估而在let表達順序進行評估。這意味着如果您綁定了多個變量,而另一個取決於另一個,則將使用在之前的範圍中的值來評估綁定。

所以,這將拋出一個異常:

(def ^:dynamic *a*) 
(def ^:dynamic *b*) 
(binding [*a* 2 
      *b* (+ *a* 3)] 
    (+ *a* *b*)) ; => ClassCastException clojure.lang.Var$Unbound cannot be cast 
       ; to java.lang.Number clojure.lang.Numbers.multiply 
       ; (Numbers.java:146) 

相反,你將不得不使用嵌套綁定形式:

(binding [*a* 2] 
    (binding [*b* (+ *a* 3)] 
    (+ *a* *b*))) ; => 8 

注意我周圍放置變數名稱「耳罩」。這是Clojure中動態變量的命名約定,所以其他人可以很容易地知道它是動態的。另外,如果你能夠動態地綁定一個var而不用聲明它的元數據,那意味着你使用了一個非常老的Clojure版本。我建議你升級 - 1.5.1是最新的穩定版本。

+0

嗨亞歷克斯,雖然沒有提到,但我得到的例外是綁定本身。我使用的Clojure是1.4。稍後將用1.5.1進行檢查。謝謝。 – JayabalanAaron

+0

我建議你編輯問題以顯示說明問題的完整示例,因爲您發佈的代碼示例對我來說不會引發異常。請參閱編輯答案以獲得一個可能的解 – Alex

+0

嵌套綁定是解決方案,它爲let做的工作的原因相同。謝謝。 – JayabalanAaron

0

在您的示例中使用binding沒有意義。只有在想要重新綁定全局變量以創建一些上下文時,才應該使用binding。在你的情況,你不需要全局變量,所以你應該使用let代替:

(ns org.jb 
    (:import (java.awt PopupMenu 
        TrayIcon 
        Toolkit 
        SystemTray) 
      (javax.swing JFrame 
         Action))) 

(defn start-app [appname icon] 
    (let [popupmenu (new PopupMenu) 
     image (.. Toolkit (getDefaultToolkit) (getImage icon)) 
     trayicon (new TrayIcon image appname popupmenu) 
     tray (. SystemTray getSystemTray)] 
    (. trayicon setImageAutoSize true)  
    (. tray add trayicon))) 

(start-app "escap" "res/escap_icon.png") 

但是,如果你決定要與binding然後Alex's answer堅持shuld幫助你解決問題。

但是你應該避免使用像binding之類的東西,除非它們是絕對必要的。

更新

如果你的目標是保存一些狀態將來的計算,然後binding將無法​​幫助你。它只會將變量與其內部的新值綁定在一起,從而使其他應用程序保持完整。

所以,當你想改變一個全球性的狀態,你應該使用alter-var-root代替:

(def ^:dynamic *app-state* {}) 

(defn set-state! [new-state] 
    (alter-var-root #'*app-state* (constantly new-state))) 

(defn update-state! [mixin] 
    (alter-var-root #'*app-state* merge mixin)) 

你也應該儘量保持你的大部分功能接近「功能範式」,你可以:

(defn start-app 
    "Creates new app with given appname and icon and returns it" 
    [appname icon] 
    (let [popupmenu (new PopupMenu) 
     image (.. Toolkit (getDefaultToolkit) (getImage icon)) 
     trayicon (new TrayIcon image appname popupmenu) 
     tray (. SystemTray getSystemTray)] 
    (. trayicon setImageAutoSize true)  
    (. tray add trayicon) 
    { :popupmenu popupmenu 
     :image image 
     :trayicon trayicon 
     :tray })) 

(update-state! (start-app "escap" "res/escap_icon.png")) 
+0

更多我使用Clojure,更多的是如果我發現讓我足夠滿足我的要求。我嘗試'綁定'的原因是我想重用現有的'狀態',現在我意識到'功能範式'違背了。感謝Leonid的指導。 – JayabalanAaron

+0

@JayabalanAaron我更新了我的答案,以防你仍想創建一些狀態。 –