【问题标题】:correct clojure binding of java instances to Vars将 java 实例正确绑定到 Vars 的 clojure
【发布时间】:2013-11-10 03:13:31
【问题描述】:

我正在将一个实例绑定到一个 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 代替绑定可以在词法范围内工作。不过想实现动态绑定。

【问题讨论】:

    标签: clojure clojure-java-interop


    【解决方案1】:

    我在这里看到的只是一个空的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 中的表达式是按顺序计算的。这意味着,如果您要绑定多个 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 是最新的稳定版本。

    【讨论】:

    • 嗨,Alex,虽然之前没有提到,但我得到的例外是绑定本身。我使用的 Clojure 是 1.4。不久将检查 1.5.1。谢谢。
    • 我建议您编辑问题以显示说明问题的完整示例,因为您发布的代码示例对我来说没有任何异常。请参阅编辑后的答案以获取一种可能的解决方案。
    • 嵌套绑定是解决方案,同样的原因它适用于 let。谢谢。
    【解决方案2】:

    在您的示例中使用 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 应该可以帮助您解决问题。

    但是你应该避免使用像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"))
    

    【讨论】:

    • 更多我使用 Clojure,如果我发现 let 足以满足我的要求,则更多。我尝试“绑定”的原因是我想重用现有的“状态”,现在我意识到这违背了“功能范式”。感谢 Leonid 的指导。
    • @JayabalanAaron 我更新了我的答案,以防你仍然想创建一些状态。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2011-11-26
    • 1970-01-01
    相关资源
    最近更新 更多