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

    

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

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

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

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)

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

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)
    

Thanks!