2

我将一个实例绑定到一个 Var:

(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)

我正在预定义 Var

(def image)

甚至尝试过

(def ^:dynamic image)

无法理解该消息的预期内容。

然而,使用 let 代替绑定在词法范围内有效。不过想实现动态绑定。

4

2 回答 2

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*))

另一个可能的问题是绑定表达式是并行计算的,而表达式 inlet是按顺序计算的。这意味着如果您要绑定多个 var 并且一个依赖于另一个,它将使用在评估绑定之前在范围内的值。

因此,这将引发异常:

(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

请注意,我在 var 名称周围放置了“耳罩”。这是 Clojure 中动态变量的命名约定,因此其他人可以很容易地看出它是动态的。此外,如果您能够动态绑定 var 而无需使用^:dynamic元数据声明它,这意味着您使用的是相当旧的 Clojure 版本。我建议您升级 - 1.5.1 是最新的稳定版本。

于 2013-10-30T15:08:50.317 回答
0

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那么亚历克斯的回答应该可以帮助您解决问题。

但是你应该避免使用类似的东西,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"))
于 2013-10-31T12:59:38.877 回答