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
(-> Functions)
; 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
FunctionsCode 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
FunctionsPolymorphism, 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)