Download - Clojure And Swing
Clojure and Swing – a new productivity sweet spot?
Simon White
Skills Matter• The UK Open Source Training Company
• Training on: Agile Development, Test Driven Development, Java, Eclipse, Spring, Hibernate, Ajax, Ruby on Rails, Struts Ti, Wicket, JavaServer Faces, Tapestry, Beehive, AOP, RIFE and more!
• Training straight from the Source• Experts write and teach our courses: Adrian
Colyer, Chad Fowler, Howard M. Lewis Ship Craig Larman, Dave Crane, Kevlin Henney, Rob Harrop, Kito Mann, Rod Johnson and many more
• Partners with leading edge companies• BEA, IBM, Interface21, Sun, Tangosol, wso2
© Catalysoft Ltd, 2010
Speaker Qualifications• Simon White, Independent Java developer• Games published 1985• PhD Artificial Intelligence• Written Scientific Software for:
– Remote Sensing– Drug Discovery– Medical & Genetics Research
• Swing Developer since 2000• Author of JIDE Charts• SkillsMatter Author & Trainer
© Catalysoft Ltd, 2010
What is Clojure?
• A functional language that runs on the JVM– A new dialect of LISP
• Not Closure• Not Clozure
MYTH: LISP is slow
MYTH: Therefore Clojure is slow
Functional Languages• What is a function?
– a mathematical relation such that each element of a given set (the domain of the function) is associated with an element of another set (the range of the function)) [Wordnet]
• Focus on the input/output relation– e.g. length of a string maps a string to an
integer• Immutability Good; Side-effects Bad
LISP Syntax• (functor param1 param2 ... paramn)
– for example, (+ 1 2)• Brackets denote evaluation• A single quote protects from evaluation
– so '(x y z) does not use x as a function• With nested expressions, evaluate inner
expression first: (+ (+ 2 3) (* 2 3)) is 11• Special forms like let, cond deviate from
the syntax and must be learned
Hello Factorial
(defn fac “Computes the factorial of n” [n] (if (= n 0) 1 (* n (fac (- n 1)))))
n! = n × (n-1) × (n-2) × ... × 1Also 0! = 1
REPLRead-Evaluate-Print-Loop
user=> (defn add-one [n] (+ 1 n))#'user/add-oneuser=> (add-one 6)7user=>
Java vs. LISP
Java LISP
Good
Mainstream LanguageUbiquitousCross-PlatformMany good librariesGood IDEsOpen Source
Powerful & ConciseExtensibleFast to developGreat for agile approaches
Bad
VerboseSlow to develop
Niche LanguageDifficult to learn?Lack of GUI library (CLIM?)
Why mix Java and LISP?
To get the best of both:• Speedy development of custom code in
LISP• Speedy development through library reuse
in Java
• Speedy development of applications with concurrent processing
LISP Promotes Agility
• LISP was agile long before agility was respected
• Easy to mix and match function application– Accommodate changing requirements
• Many small functions => very reusable code• REPL Encourages ad-hoc testing• Functional style makes it easy to write unit
tests
Clojure as a LISP dialect
• Simplified syntax compared to Common LISP– cond, let
• Not object-oriented– But you can define structures and multi-methods
• No multiple value returns– Arguably syntactic sugar anyway
• Lazy evaluation of sequences• Destructuring
– Structural pattern matching on function parameters• Concurrency
Data Structures 1: Lists
• (list 'a 'b 'c) → (a b c)• (first '(a b c)) → a• (rest '(a b c)) → (b c)• (nth '(a b c) 1) → b• (cons 'a '(b c)) → (a b c)• (concat '(a b) '(c d)) → (a b c d)
Data Structures 2: Vectors
• (vector 'a 'b 'c) → [a b c]• (first '[a b c]) → a• (rest '[a b c]) → (b c)• (nth '[a b c] 1) → b• (cons 'a '[b c]) → (a b c)• (conj '[a b] 'c) → [a b c]• (concat '[a b] '[c d]) → (a b c d)
Data Structures 3: Maps
Key-Value Pairs• (get '{a 1, b 2} 'b) → 2• (assoc '{a 1, b 2} 'c 3) → {c 3, a 1, b 2}• (dissoc '{a 1, b 2} 'b) → {a 1}• (defstruct book :title :author)• (struct-map book :title "Jungle
Book" :author "Rudyard Kipling") • (bean obj)
Everything is a Sequence
• Lists, Vectors and Maps are all sequences• first, rest & cons always return a sequence
• (range 5) → (0 1 2 3 4)• (take 2 (range 5)) → (0 1)• (take 3 (repeat 'x)) → (x x x)
Predicates (Boolean-Valued Tests)
• (= a 10)• (identical? a b)• (even? a)• (odd? a)• (integer? a)
Decisions
• (if (= a 1) 5 0)• (cond
(= a 1) 5
(= a 2) 10
true 0)• (when (= a 1) 5)
a == 1 ? 5 : 0 Java
"Iteration"
• (for [a '(1 2 3)] (+ a 1)) → (2 3 4)• (doseq [a (range 5)] a) → nil• (doseq [a (range 3)] (println a)) →
0
1
2
nil
Anonymous Functions
user=> (fn [a b] (+ a b 1))
#<user$eval__101$fn__103 ...@a613f8>
user=> ((fn [a b] (+ a b 1)) 3 4)
8
user=> #(+ %1 %2)
#<user$eval__121$fn__123 ...@56f631>
Higher Order Functions
• (inc 1)→2• (map inc '(1 2 3))→(2 3 4)• (map + '(1 2 3) '(10 11 12))→(11 13 15)• (max 1 2 3)→3• (apply max '(1 2 3))→3• (filter even? (range 10))→(0 2 4 6 8)
Partial Functions
Partial returns a function
user=> (def add-two (partial + 2))
#'user/add-two
user=> (add-two 5)
7
Java InteroperabilityOp Type Java Clojure
Construction new JPanel() (new JPanel)or(JPanel.)
Static Field Access System.out System/out
Math.PI Math/PI
Static Method Call System.getProperties() (System/getProperties)
System.exit(0) (System/exit 0)
Method Call frame.setVisible(true) (.setVisible frame true)
frame.setSize(600, 400) (.setSize frame 600 400)or(. frame setSize 800 600)
A GUI in a functional language?
• Functional languages use functions as mathematical models of computation:
f(x1, x2, ...) → x’
• Great for factorials, but how does it work with user-interfaces?– Forms?– Keyboard & Mouse?– Events?
Need to Think Differently
• Values of functions ‘computed’ as the result of a user-interaction
• Consider y-or-n-p
CL> (y-or-n-p "Do you really want to quit?")
T
Modelling Change of State
Name: “George”DOB: “11-Jul-73”
Address: “16 Goldman Square”
Name: “George”DOB: “11-Jul-73”Address: “3 Elm
Avenue”
f(x) → x’
This is a new (immutable) value,not a modified one
Java Swing: Create a JFrameimport javax.swing.JFrame;
public class MyClass {... public static void main(String[] args) { JFrame frame = new JFrame("My Frame"); frame.setBounds(300, 300, 600, 400); frame.setVisible(true); }}
Clojure Swing: Create a JFrame
(ns user (:import (javax.swing JFrame)))
(defn make-frame [] (let [f (JFrame. "My Frame")] (.setBounds f 300 300 600 400) (.setVisible f true) f ) )
f is the return value
Alternative: Use doto
(defn make-frame [] (let [f (JFrame. "My Frame")] (doto f (.setBounds 300 300 600 400) (.setVisible true))))
Create a Panel & Button
(ns user (:import java.awt.FlowLayout (javax.swing JButton JPanel)))
(defn make-panel [] (let [panel (JPanel. (FlowLayout.)) button (JButton. "Press Me")] (doto panel (.add button))))
Make a Button do Something
(defn make-panel2 [] (let [panel (JPanel. (FlowLayout.)) button (JButton. "Press Me")] (.addActionListener button (proxy [ActionListener] [] (actionPerformed [e] (println e)))) (doto panel (.add button)) ) )
#<ActionEvent java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=Press Me...
Clojure Custom Component
(defn make-component [msg] (proxy [javax.swing.JComponent] [] (paintComponent [g] (let [height (.getHeight this) width (.getWidth this)] (doto g ... )))))
Detail on next slide
Custom Component (cont/d)
(doto g (.setColor Color/gray) (.fillOval 20 20 (- width 20) (- height 20)) (.setColor Color/yellow) (.fillOval 0 0 (- width 20) (- height 20)) (.setFont (.deriveFont (.getFont g) (float 50))) (.setColor Color/black) (.drawString msg (int (/ (- width (.stringWidth (.getFontMetrics
g) msg)) 2)) (int (/ height 2))))
Three ways in which Clojure can help Swing
1. Reducing boiler-plate code– Easy to write code that generates the
required patterns
2. Definitions of actions and easier binding– Easier to separate configuration (name, icon,
keyboard shortcut, ...) from code hook
3. Flexibility and Reusability– Using functions as parameters for user
customisation of behaviour
1. Reducing Boiler-Plate CodeGridBagConstraints c = new GridBagConstraints();
c.weighty = 1.0;
c.weightx = 1.0;
c.insets = new Insets(5, 5, 5, 5);
c.gridx = 0;
c.gridy = 0;
c.gridheight = 2;
c.anchor = GridBagConstraints.CENTER;
c.fill = GridBagConstraints.BOTH;
add(leftListPane, c);
c.gridx = 1;
c.gridy = 0;
c.gridheight = 1;
...
• GridBagConstraints are unwieldy
• Use the same options a lot of the time
• Difficult to understand
Using a grid-bag-layout macro
(def panel (doto (JPanel. (GridBagLayout.)) (grid-bag-layout :fill :BOTH, :insets (Insets. 5 5 5 5) :gridx 0, :gridy 0 (JButton. "One") :gridy 1 (JButton. "Two") :gridx 1, :gridy 0, :gridheight 2 (JButton. "Three"))))
See http://stuartsierra.com/2010/01/05/taming-the-gridbaglayout
2. Defining Swing Actions
• Swing Actions are a nice idea, but have problems.
• Typically, you might have:– Lots of inner classes with repeated boiler
plate code, or:– Separate classes that extend AbstractAction
but are too tightly coupled to a main class so that they have the necessary context for the actionPerformed() method.
Clojure Actions: Defer binding for actionPerformed method
• With an Action in Clojure it’s possible to preset the ‘constant’ variables like Name, Icon, Accelerator ; but defer the binding for the handler.
• This means we can easily separate the creation of an action according to some template from its binding to a response function.
Create an Action when the handler function is known
(defmacro copy-action [handler] `(make-action {:name "Copy" :command-key "copy" :icon (ImageIcon. (fetch-image "copy.png")) :handler ~handler :mnemonic (mnemonic \C) :accelerator (accelerator "ctrl C")}))
3. Flexibility and Reusability
(def *tracing-actions* true) (defn trace-action [handler] (fn [#^ActionEvent e] (try (when *tracing-actions* (print "Doing" (.getActionCommand e))) (handler e) (finally (when *tracing-actions* (println "Done"))))))
Swing Worker & Busy Cursor
(defmacro with-busy-cursor [component f] `(proxy [SwingWorker] [] (doInBackground [] (.setCursor ~component
(Cursor/getPredefinedCursor Cursor/WAIT_CURSOR)) ~f) (done [] (.setCursor ~component
(Cursor/getDefaultCursor)))))
(with-busy-cursor chart (load-file data-file))
Clojure/Swing in the Real World
Creating a Chart Model
(defn make-model "Create and return a ChartModel using the supplied
rows and picking out the x and y columns" [model-name rows #^String x-col #^String y-col] (let [model (DefaultChartModel. model-name)] (doseq [row rows] (let [x (get row x-col) y (get row y-col)] (when (and (number? x) (number? y)) (.addPoint model (double x) (double y)))) ) model))
Summary
• Clojure is Powerful and Flexible• Excellent Java Interoperability• Opportunities to apply LISP power to
Swing GUI development• Can be used for Real World Applications• A Secret Weapon for Productivity?