clojure and swing

44
Clojure and Swing – a new productivity sweet spot? Simon White

Upload: skills-matter

Post on 24-Jun-2015

6.701 views

Category:

Technology


1 download

DESCRIPTION

Clojure is a new dialect of LISP that runs on the Java Virtual Machine (JVM). As a functional language, it offers great benefits in terms of programmer productivity; as a language that runs on the JVM, it also offers the opportunity to reuse existing Java libraries. Simon’s interest is in using Clojure to build desktop applications with the Java Swing GUI library. In this presentation Simon discusses how the power of Clojure can be applied to Swing, and whether it hits the sweet spot.

TRANSCRIPT

Page 1: Clojure And Swing

Clojure and Swing – a new productivity sweet spot?

Simon White

Page 2: Clojure And Swing

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

Page 3: Clojure And Swing

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

Page 4: Clojure And Swing

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

Page 5: Clojure And Swing

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

Page 6: Clojure And Swing

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

Page 7: Clojure And Swing

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

Page 8: Clojure And Swing

REPLRead-Evaluate-Print-Loop

user=> (defn add-one [n] (+ 1 n))#'user/add-oneuser=> (add-one 6)7user=>

Page 9: Clojure And Swing

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?)

Page 10: Clojure And Swing

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

Page 11: Clojure And Swing

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

Page 12: Clojure And Swing

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

Page 13: Clojure And Swing

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)

Page 14: Clojure And Swing

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)

Page 15: Clojure And Swing

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)

Page 16: Clojure And Swing

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)

Page 17: Clojure And Swing

Predicates (Boolean-Valued Tests)

• (= a 10)• (identical? a b)• (even? a)• (odd? a)• (integer? a)

Page 18: Clojure And Swing

Decisions

• (if (= a 1) 5 0)• (cond

(= a 1) 5

(= a 2) 10

true 0)• (when (= a 1) 5)

a == 1 ? 5 : 0 Java

Page 19: Clojure And Swing

"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

Page 20: Clojure And Swing

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>

Page 21: Clojure And Swing

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)

Page 22: Clojure And Swing

Partial Functions

Partial returns a function

user=> (def add-two (partial + 2))

#'user/add-two

user=> (add-two 5)

7

Page 23: Clojure And Swing

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)

Page 24: Clojure And Swing

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?

Page 25: Clojure And Swing

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

Page 26: Clojure And Swing

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

Page 27: Clojure And Swing

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); }}

Page 28: Clojure And Swing

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

Page 29: Clojure And Swing

Alternative: Use doto

(defn make-frame [] (let [f (JFrame. "My Frame")] (doto f (.setBounds 300 300 600 400) (.setVisible true))))

Page 30: Clojure And Swing

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))))

Page 31: Clojure And Swing

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...

Page 32: Clojure And Swing

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

Page 33: Clojure And Swing

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))))

Page 34: Clojure And Swing

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

Page 35: Clojure And Swing

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

Page 36: Clojure And Swing

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

Page 37: Clojure And Swing

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.

Page 38: Clojure And Swing

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.

Page 39: Clojure And Swing

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")}))

Page 40: Clojure And Swing

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"))))))

Page 41: Clojure And Swing

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))

Page 42: Clojure And Swing

Clojure/Swing in the Real World

Page 43: Clojure And Swing

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))

Page 44: Clojure And Swing

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?