Lisp + JVM
or JavaScript
or Python
or Scheme
or Lua ...
It's all about evaluation
; integer user=> 42 42 ; double user=> 3.14159 3.14159 ; string user=> "GLFPC" "GLFPC"
; keyword user=> :my-keyword :my-keyword ; also: regex, rationals, bignums, etc.
; a function that prints a greeting user=> (fn [s] (println "Hello, " s)) #<user$eval560$fn...> ; a function that prints a greeting user=> #(println "Hello, " %) #<user$eval560$fn...>
user=> (def pi 3.14) #'user/pi user=> pi 3.14
; vector user=> ["hi" 99 pi] ["hi" 99 3.14] ; map user=> {:foo pi, 100 "bar"} {:foo 3.14, 100 "bar"} ; set user=> #{1 pi 3} #{1 3.14 3}
; a list is a function call user=> (+ 1 2) 3 user=> ((fn [a b] (+ a b)) 1 2) 3
Now you know Clojure :)
Learn clojure -> abstraction -> win
; Don't do this (doto (javax.swing.JLabel.) (.setText "hi") (.setColor java.awt.Color/BLUE) (.setFont (... some horrible code ...)))
; Do this (label :text "hi" :color :blue :font "ARIAL-BOLD-18")
; An error dialog (border-panel :center (label :text "Unknown error -11!") :south (flow-panel :items (map (partial button :text) ["Abort" "Retry" "Ignore"])))
composition, partial application, higher-order functions
(.addMouseListener my-widget (proxy java.awt.event.MouseAdapter [] [] (mouseClicked [event] ... do something interesting ...)))
(listen my-widget :mouse-clicked (fn [event] ... do something interesting ...))
(cond (= x :foo) 1 (= x :bar) 2 (= x :yum) 3 :else nil)
(get {:foo 1 :bar 2 :yum 3} x) ; ... or better yet ... ({:foo 1 :bar 2 :yum 3} x)
(defn make-mouse-listener [f] (reify java.awt.event.MouseListener (mouseClicked [e] (f e)) (mousePressed [e] (f e)) ... and so on ...)) (defn make-mouse-motion-listener [f] (reify java.awt.event.MouseMotionListener (mouseMoved [e] (f e)) (mouseDragged [e] (f e)) ... and so on ...))
(def descriptors [{:name :mouse :interface java.awt.event.MouseListener :events [:mouse-clicked :mouse-pressed ...]}, {:name :mouse-motion :interface java.awt.event.MouseMotionListener :events [:mouse-moved :mouse-dragged ...]} ...])
; Make a macro to write the code for us (defmacro reify-listener [descriptor] `(defn ... a slightly scary macro ...)) ; Call the macro for each descriptor (doseq [descriptor descriptors] (reify-listener descriptor))
user=> (show-events my-text-widget) :action [java.awt.event.ActionListener] :action-performed :caret [javax.swing.event.CaretListener] :caret-update :component [java.awt.event.ComponentListener] :component-hidden :component-moved :component-resized :component-shown ... snip a lot here ... :mouse-motion [java.awt.event.MouseMotionListener] :mouse-dragged :mouse-moved :mouse-wheel [java.awt.event.MouseWheelListener] :mouse-wheel-moved :property-change [java.beans.PropertyChangeListener] :property-change :selection [javax.swing.event.CaretListener] :caret-update
Code in. Code out.
; Invoke a function sometime on the UI thread (invoke-later* (fn [] (do-a) (do-b) (do-c)))
(defmacro invoke-later [& body] `(invoke-later* (fn [] ~@body))) ; usage (invoke-later (do-a) (do-b) (do-c))
Typing isn't as bad as using the mouse, but it's still takes time
; roughly like this (defmacro reify-listener [desc] `(defn ~(->> (:name desc) name (str "make-") symbol) [f#] (reify ~(:interface desc) ~@(for [e (:events desc)] `(~(camel-case e) [event#] (f# e#))))))
Restrain yourself
Polymorphism, a la carte
; A small collection of related, ; polymorphic functions (defprotocol Selection (get-selection [this]) (set-selection [this value]))
; On a new type (defrecord MyType [fields] Selection (get-selection [this] ... MyType specific impl here ...) (set-selection [this value] ... MyType specific impl here ...))
; On an *existing* type (extend-protocol Selection javax.swing.JList (get-selection [this] ... JList specific impl here ...) (set-selection [this value] ... JList specific impl here ...))
; On a new anonymous type (defn make-selectable [args] (reify Selection (get-selection [this] ... specific impl here ...) (set-selection [this value] ... specific impl here ...)))
; Extend a new abstraction over all Swing ; widget types (defprotocol Value (get-value [this]) (set-value [this new-value]) (extend-protocol Value javax.swing.text.TextComponent (get-value [this] (.getText this)) (set-value [this new-value] (.setText this new-value) javax.swing.JSlider (get-value [this] (.getValue this)) (set-value [this new-value] (.setValue this new-value) ...)
; Extension points for custom and ; third-party widgets (config! my-custom-widget :custom-property "some value")
user=> (show-options (label)) Option Notes/Examples ------------------- -------------- :background :aliceblue "#f00" "#FF0000" (seesaw.color/color 255 0 0 0 224) :border 5 "Border Title" [5 "Compound" 10] See (seesaw.border/*) :bounds :preferred [x y w h] Use :* to leave component unchanged: ... snip a lot here... :user-data Anything. Associate arbitrary user-data with a widget. See (seesaw.core/user-data) :v-text-position :bottom :center :top :valign :bottom :center :top :visible? boolean
; Introducing missing interfaces to Swing ... ; ... aka eliminating reflection (.getText my-widget) ;=> reflection warning (defprotocol Text (get-text [this])) (extend-protocol JTextField ... JButton ... JLabel) (get-text my-widget) ;=> NO reflection warning
as implementation detail ...
(defprotocol Value (get-value* [this]) (set-value* [this new-value]) ... (defn get-value [something] (get-value* (to-widget something)))
That's not all.
// List JList myList = ...; myList.addListSelectionListener(...); // Tree JTree myTree = ...; myTree.addTreeSelectionListener(...); // Table JTable myTable = ...; myTable.getListSelectionModel().addListSelectionListener(...); // Group of mutually exclusive radio buttons Enumeration<AbstractButton> bs = myButtons.getElements(); for(; bs.hasMoreElements();) { bs.nextElement().addItemListener(...); }
(listen my-widget :selection (fn [e] ...))
(def volume-control (slider :min 0 :max 11 :value 5)) (def volume-atom (atom 5)) (b/bind volume-control volume-atom volume-control)