Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation. For the best experience please use the latest Chrome or Safari browser. Firefox 10 (to be released soon) will also handle it.

Practical Abstraction with Clojure Great Lakes Functional Programming Conference 2012 Dave Ray (@darevay)

Clojure Lisp + JVM

or JavaScript

or Python

or Scheme

or Lua ...

Primer It's all about evaluation

Evaluation - Primitives ; integer user=> 42 42 ; double user=> 3.14159 3.14159 ; string user=> "GLFPC" "GLFPC"

Evaluation - Specials ; keyword user=> :my-keyword :my-keyword ; also: regex, rationals, bignums, etc.

Evaluation - Functions ; 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...>

Evaluation - Symbols user=> (def pi 3.14) #'user/pi user=> pi 3.14

Evaluation - Composites ; 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}

Evaluation - Lists But what about the parens?!? ; a list is a function call user=> (+ 1 2) 3 user=> ((fn [a b] (+ a b)) 1 2) 3 But what about the parens?!?

Congratulations Now you know Clojure :)

The Horror of Swing™ Learn clojure -> abstraction -> win

Abstraction with Clojure Simplicity, a la carte

Abstraction with Clojure (-> Functions)

Data

Macros

Protocols

DataMacrosProtocols

Construct a Widget ; Don't do this (doto (javax.swing.JLabel.) (.setText "hi") (.setColor java.awt.Color/BLUE) (.setFont (... some horrible code ...)))

Construct a Widget ; Do this (label :text "hi" :color :blue :font "ARIAL-BOLD-18")

Compose Some Widgets ; 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

Event Handling (.addMouseListener my-widget (proxy java.awt.event.MouseAdapter [] [] (mouseClicked [event] ... do something interesting ...)))

Event Handling (listen my-widget :mouse-clicked (fn [event] ... do something interesting ...))

Abstraction with Clojure Functions

(-> Data)

Macros

Protocols

FunctionsMacrosProtocols

Executable code ... (cond (= x :foo) 1 (= x :bar) 2 (= x :yum) 3 :else nil)

... or data? (get {:foo 1 :bar 2 :yum 3} x) ; ... or better yet ... ({:foo 1 :bar 2 :yum 3} x)

Executable code ... (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 ...))

... or data? (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 ...]} ...])

... or data? ; 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))

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

Data Declarative programming. Programming with data.

Abstraction with Clojure Functions

Data

(-> Macros)

Protocols



FunctionsDataProtocols Code in. Code out.

Creating New Syntax ; Invoke a function sometime on the UI thread (invoke-later* (fn [] (do-a) (do-b) (do-c)))

Creating New Syntax (defmacro invoke-later [& body] `(invoke-later* (fn [] ~@body))) ; usage (invoke-later (do-a) (do-b) (do-c))

Boilerplate Elimination 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#))))))

Macros Restrain yourself

Abstraction with Clojure Functions

Data

Macros

(-> Protocols)



FunctionsDataMacros Polymorphism, a la carte

Protocol ; A small collection of related, ; polymorphic functions (defprotocol Selection (get-selection [this]) (set-selection [this value]))

Protocols ; On a new type (defrecord MyType [fields] Selection (get-selection [this] ... MyType specific impl here ...) (set-selection [this value] ... MyType specific impl here ...))

Protocols ; 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 ...))

Protocols ; On a new anonymous type (defn make-selectable [args] (reify Selection (get-selection [this] ... specific impl here ...) (set-selection [this value] ... specific impl here ...)))

Protocols in Seesaw

Protocols in Seesaw ; 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) ...)

Protocols in Seesaw ; Extension points for custom and ; third-party widgets (config! my-custom-widget :custom-property "some value")

bonus 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

Protocols in Seesaw ; 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

Protocols as implementation detail ... (defprotocol Value (get-value* [this]) (set-value* [this new-value]) ... (defn get-value [something] (get-value* (to-widget something)))

Abstraction with Clojure Functions

Data

Macros

Protocols

(-> more...)



FunctionsDataMacrosProtocols(-> more...) That's not all.

Selection events // 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(...); }

Selection Events (listen my-widget :selection (fn [e] ...))

Widget Binding (def volume-control (slider :min 0 :max 11 :value 5)) (def volume-atom (atom 5)) (b/bind volume-control volume-atom volume-control)