правильная привязка clojure экземпляров java к Vars

Я привязываю экземпляр к 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 вместо привязки работает в пределах лексической области видимости. Однако хочу добиться динамической привязки.


person JayabalanAaron    schedule 30.10.2013    source источник


Ответы (2)


Все, что я вижу здесь, это пустая форма binding без кода. Привязка переменной выходит за пределы области действия, как только вы покидаете форму binding. Судя по вашему сообщению об ошибке, вы пытаетесь использовать переменную image вне формы привязки. Вы должны убедиться, что весь код, использующий 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

Обратите внимание, что я поместил «наушники» вокруг имени var. Это соглашение об именах для динамических переменных в Clojure, поэтому другие люди могут легко сказать, что это динамическое. Кроме того, если вы можете динамически связать переменную без объявления ее метаданными ^:dynamic, это означает, что вы используете довольно старую версию Clojure. Я бы посоветовал вам обновиться — 1.5.1 — это последняя стабильная версия.

person Alex    schedule 30.10.2013
comment
Привет, Алекс, не упомянул ранее, исключение, которое я получаю, связано с самой привязкой. Я использую Clojure версии 1.4. Скоро проверю с 1.5.1. Спасибо. - person JayabalanAaron; 31.10.2013
comment
Я предлагаю вам отредактировать вопрос, чтобы показать полный пример, иллюстрирующий проблему, потому что опубликованный вами пример кода не создает для меня никаких исключений. См. отредактированный ответ для одного возможного решения. - person Alex; 31.10.2013
comment
Вложенная привязка была решением, по той же причине, по которой она работала для let. Спасибо. - person JayabalanAaron; 31.10.2013

Нет смысла использовать 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, то ответ Алекса должен помочь вам с вашей проблемой.

Но вам следует избегать использования таких вещей, как 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"))
person Leonid Beschastny    schedule 31.10.2013
comment
Больше я использую Clojure, больше, если найду достаточно для моих требований. Причина, по которой я пытался «привязать», заключалась в том, что я хотел повторно использовать существующее «состояние», теперь, когда я понимаю, что это противоречит «функциональной парадигме». Спасибо Леонид за направление. - person JayabalanAaron; 03.11.2013
comment
@JayabalanAaron Я обновил свой ответ на случай, если вы все еще хотите создать какое-то состояние. - person Leonid Beschastny; 03.11.2013