Chapter 10. Java.next

 

 

Regardless of your views on the Java language itself, it’s difficult to deny that the JVM is a stellar piece of software. The confluence of the just-in-time (JIT) compiler, garbage collection, HotSpot, and the flexible bytecode have created an environment that many programmers have chosen to grow their alternative programming languages. Additionally, the deluge of library options hosted on the JVM further make the JVM the language target of choice. From Clojure to Groovy to Scala to Fantom to Frink to Ioke to Jess to JRuby to Jython, there seems to be no lack of options for the enthusiastic polyglot programmer. We may soon see job listings for “JVM programmers.” But where does that leave Java the programming language?

Java the language isn’t dead.

The JVM is optimized for running Java bytecode, and only recently[1] have Java.next languages been a consideration. You may ask yourself whether JVM bytecode is equivalent to Java source code, and the answer is no. Instead, languages such as Clojure and Scala compile directly to bytecode and can access Java compiled libraries as needed. Because of their reliance on the JVM as the runtime environment, Clojure and the other Java.next languages will be fundamentally constrained by the limitations of the JVM itself. The limitations of the JVM as defined by the limitations of the Java language specification set the beat by which the Java.next languages dance. Java isn’t dead; it’s alive and well, and it runs the show.

1 More details can be found in JSR-000292, “Supporting Dynamically Typed Languages on the Java Platform.”

 

The Java.Next Mantra

The apprentice avoids all use of Java classes. The journeyman embraces Java classes. The master knows which classes to embrace and which to avoid.

 

An expert understanding of the Java Virtual Machine isn’t required for writing powerful applications in Clojure, but it’ll help when issues stemming from host limitations arise. Thankfully, Clojure does a good job of mitigating many of the limitations inherent in its host, but some are too deeply embedded in the fibers of the JVM to avoid. Clojure provides a specific set of interoperability tools: gen-class, proxy, definterface, its exceptions facility, and a host of array functions. We’ll touch on each of these in turn, but we’ll begin with the creation of anonymous objects using proxy.

10.1. Generating objects on the fly with proxy

There’s a saying within the Clojure community stating (Halloway 2009) that Clojure does Java better than Java. This is a bold statement, but not one without merit, as we’ll show throughout this chapter. Java programmers are accustomed to drawing a severe distinction between development time and runtime. Using Clojure’s proxy feature allows you to blur this distinction.

 

Clojure Aphorism

Many software projects require a lot of planning because their implementation languages don’t foster change. Clojure makes it a lot easier to plan for change.

 

Clojure’s proxy mechanism is meant strictly for interoperability purposes. In section 9.3, we discussed how reify was intended to realize a single instance of a type, protocol, or interface—in other words, abstractions. But when dealing with Java libraries, you’re at times required to extend concrete classes, and it’s in this circumstance where proxy shines. Be aware that by using proxy, you bring a lot of Java’s semantics into your Clojure programs. Though extending concrete classes is seen often in Java, doing so in Clojure is considered poor design, leading to fragility, and should therefore be restricted to those instances where interoperability demands it.

10.1.1. A simple dynamic web service

Using Clojure breaks the ponderous code/compile/run development cycle by adding an element of dynamism into the fold. Take for example a scenario where we want to develop a web service using an existing Java 1.5 API.

Listing 10.1. A simple dynamic web service

After entering the code in listing 10.1, you should see the message “Hello Cleveland” in your web browser at address http://localhost:8123/joy/hello. This is only marginally interesting, especially because the source is organized in a way that doesn’t take advantage of Clojure’s flexibility.

If we instead organize the code to bind the return of default-handler, we can manipulate the handler independently and update its behavior at runtime, as shown:

(.stop server 0)

(def p (default-handler
         "There's no problem that can't be solved
          with another level of indirection"))

(def server (new-server 8123 "/joy/hello" p))

At this point, visiting the aforementioned URL will show the new message, making this simple server more compelling. But we can take it one step further by making changes without taking the server instance down in such a clumsy fashion. Ideally, we’d like to be able to call a function to change the message at any time:

(change-message p "Our new message")

The implementation of change-message is given in the following listing.

Listing 10.2. Convenience functions for changing the web service message

We’ve added a few extras to the implementation that will be useful later, but for now concentrate on the fact that change-message calls the function update-proxy with the proxy object p and a map containing an anonymous function keyed on a string referencing a method name to override. The anonymous function looks similar to the handle method defined in the returned proxy from the original default-handler function, with some extras added for flexibility’s sake. You can test this by entering the following function call:

(change-message p "Hello Dynamic!")

Refreshing your browser will reflect the change made by displaying the string "Hello Dynamic!". If so inclined, you can also inspect the current proxy mappings using the function proxy-mappings. The question remains—how does update-proxy change the behavior of a previously generated proxy class?

It’s Called Proxy for a Reason

As we mentioned, the proxy function generates the bytecode for an actual class on demand, but it does so in such a way to provide a more dynamic implementation. Instead of inserting the bytecode for the given function bodies directly into the proxy class, Clojure instead generates a proper proxy in which each method looks up the function implementing a method in a map. This trades highly useful dynamic behavior for some runtime cost, but in many cases this is a fair trade.

Based on the method name, the corresponding function is retrieved from a map and invoked with the this reference and the argument(s).

Proxies for True Power Dynamism

Working from the abstract model in figure 10.1, observe how Clojure updates the mapped functions within a proxy at runtime. This web service is a humble example, but there’s a point to take away from this exercise: to perform this same task in Java isn’t impossible but would require an enormous amount of scaffolding to implement properly, whereas in Clojure it’s built into the language.

Figure 10.1. Proxy lookup: the instance returned by proxy is a proper proxy that does method dispatch to functions in a lookup table. These functions can therefore be swapped out with replacements as needed.

Proxies as Proper Citizens

In the original change-message function, we provided a hook named fltr that took the result of the call to the .getResponseBody method. Because the result of this method call is a java.io. OutputStream, we can use that information to our advantage when creating a filtering function. The use of the identity function as the default filter ensures that the usage doesn’t break in the default case; but if we’re to utilize our own filtering function, we must ensure that we properly wrap the original, which again is a perfect use case for proxy. A simple implementation of a screaming-filter would be implemented as such:

(defn screaming-filter [o]
  (proxy [FilterOutputStream] [o]
    (write [b]
      (proxy-super write (.getBytes (str "<strong>"
                                         (.toUpperCase (String. b))
                                         "</strong>"))))))

The proxy returned by screaming-filter extends the Java class java.io.FilterOutputStream to the superclass constructor (via the [o] vector). It passes the argument o, which corresponds to the OutputStream obtained from the .getResponseBody method.

 

Anaphoric Proxy

In section 8.5, we mentioned that it’s non-idiomatic to write anaphoric macros, yet you might’ve noticed that proxy is a contradiction of that statement. The use of the anaphora this is subject to the same nesting limitations as previously mentioned and is a good candidate for change in later versions of Clojure. You might notice that the reify macro, though similar to proxy, doesn’t use an anaphoric this but instead requires that it be named explicitly—the preferred approach for your own, and likely the way forward for all future Clojure core macros.

 

The call to the proxy-super function is similar to Java’s super.method() semantics. If we now execute the call to change-message passing in screaming-filter, we’ll see the expected filtered message in all caps and bold on a browser refresh:

(change-message p screaming-filter "whisper")

Note that in a break from almost every other construct in Clojure, proxy-super is not thread-safe. If some other thread were to call this proxy instance’s write method while proxy-super was still running, the base class’s method would be called directly, incorrectly skipping the proxy implementation. So be careful using proxy-super and multiple threads in close proximity to each other.

Final Points About Proxy

Clojure’s proxy capabilities are truly dynamic, allowing you to create fully stubbed proxies using either construct-proxy, get-proxy-class, or init-proxy. In both cases, a partially to fully realized proxy will be constructed, allowing programmatic customization using update-proxy and arbitrary mixin maps.

There’s a universe of difference between the code outlined in this subsection and systems employing true code hot-loading, but it’s a reasonable facsimile. Using proxy is powerful, but doing so creates unnamed instances unavailable for later extension. If you instead wish to create named classes then you’ll need to use Clojure’s gen-class mechanism, which we’ll discuss next.

10.2. Clojure gen-class and GUI programming

In section 9.1, we mentioned that Clojure namespaces can be used as the basis for generating a named class. In this section, we’ll address this topic and others related to Clojure’s gen-class function and :gen-class namespace directive in the context of writing a simple graphical user interface (GUI) library.

10.2.1. Namespaces as class specifications

Similarly to the ns example in section 9.1, the explanation of gen-class begs a declarative approach for a namespace defining a class named joy.gui.DynaFrame. We’d like this class to extend javax.swing.JFrame and declare the Vars providing its overriding method implementations to be prefixed[1] by the symbol df-. In addition, we’d like the class to implement the clojure.lang.IMeta interface. We’d also like a place to store information about instances of this class in state and would like the initialization function called on construction to be named df-init. We’d like to define a single constructor, taking a string and passing it onto the superclass constructor also taking a string. We then want to declare two public methods: the first named display taking a java.awt.Container and returning void, and the second static method version taking no arguments and returning a string. Finally, we’ll declare the required imports needed.

1 If you don’t specify a :prefix, then the default - will be used.

The worded DynaFrame class declaration is complex but has the advantage of having a direct code translation, as shown next.

Listing 10.3. The DynaFrame class namespace declaration

You can compile this namespace by saving it in a directory joy/gui, located on the classpath, in a file named DynaFrame.clj and executing the function (compile 'joy.gui.DynaFrame) in a fresh REPL. This allows a compiled class to be immediately available. But trying to create an instance in the same REPL will prove fruitless:

(joy.gui.DynaFrame. "1st")

; java.lang.UnsupportedOperationException:
;   joy.gui.DynaFrame/df-init not defined

Clearly we haven’t defined the df-init function, so we’ll do that now by switching to the joy.gui.DynaFrame namespace, defining it outright:

(in-ns 'joy.gui.DynaFrame)

(defn df-init [title]
  [[title] (atom {::title title})])

Now run the following in your REPL:

(joy.gui.DynaFrame. "2nd")

; java.lang.UnsupportedOperationException:
;   meta (joy.gui.DynaFrame/df-meta not defined?)

Because we told the Clojure compiler that the class should implement the IMeta interface, we should’ve provided a concrete implementation, which you can do at the REPL:

(defn df-meta [this] @(.state this))
(defn version [] "1.0")

As an added bonus, we implemented the static method version. To see the effects of these functions, execute the following:

(meta (joy.gui.DynaFrame. "3rd"))
;=> {:joy.gui.DynaFrame/title "3rd"}

(joy.gui.DynaFrame/version)
;=> "1.0"

We’ve filled in most of the implementation of the DynaFrame class except for the display function, which you can implement as follows:

(defn df-display [this pane]
  (doto this
    (-> .getContentPane .removeAll)
    (.setContentPane (doto (JPanel.)
                       (.add pane BorderLayout/CENTER)))
    (.pack)
    (.setVisible true)))

You can see df-display in action within the REPL by running the following:

(def gui (joy.gui.DynaFrame. "4th"))

(.display gui (doto (javax.swing.JPanel.)
                (.add (javax.swing.JLabel. "Charlemagne and Pippin"))))

This will now display the GUI frame seen in figure 10.2.

Figure 10.2. A simple use of DynaFrame: now that you’ve compiled the DynaFrame class, you can start using it to display simple GUIs.

And because it’s a DynaFrame we should be able to change it on the fly, right? Right:

(.display gui (doto (javax.swing.JPanel.)
                (.add (javax.swing.JLabel. "Mater semper certa est." ))))

This will change the view to that in figure 10.3.

Figure 10.3. A simple dynamic update of DynaFrame: we can update the DynaFrame on the fly without restarting.

But now that you have this interesting little frame, what can you do with it? Next, we’ll experiment with DynaFrame as the foundation for agile GUI prototyping.

The Guts of Namespace Compilation

So what exactly does the :gen-class directive provide in terms of generated class files? With or without :gen-class, Clojure will generate a set of classes corresponding to each function in a namespace. For the function joy.gui.DynaFrame/df-dis-play, a class file will be generated on the classpath of joy.gui.DynaFrame$df_display containing (at least) a method invoke, at the location CLASSPATH/ joy/gui/DynaFrame$df_display.class, as shown:

package joy.gui;
public class DynaFrame$df_display extends AFunction {
    . . .
    public Object invoke(Object that, Object container) {
        . . . display actions . . .
    }
}

Of course, this describes implementation details and shouldn’t be considered fact in future version of Clojure. In fact, as shown before, you were able to add implementations for the parts of the DynaFrame class at the REPL because Clojure generates a stub that looks up concrete implementations through Vars. But these details are useful for describing the logical product of :gen-class and compile. The :gen-class directive with the argument :name joy.gui.DynaFrame creates a class vaguely resembling the following Java source:

package joy.gui;

public class DynaFrame extends javax.swing.JFrame {
    public final Object state;
    public DynaFrame(String title) {
      Object r =  clojure.lang.RT.var("joy.gui.DynaFrame", "df-init")
                     .invoke(title);
      Object cargs = clojure.lang.RT.nth(r, 0);
      state = clojure.lang.RT.nth(r, 1);
      super((String) clojure.lang.RT.nth(cargs, 0));
    }

    public static String version() { return "1.0"; }

    // Delegate to the display function var
    public void display(Object the_this, java.awt.Container c) {
        return clojure.lang.RT.var("joy.gui.DynaFrame", "df-display")
                 .invoke(the_this, c);
    }

    . . .
}

The :gen-class directive creates a class that’s a delegate for the Vars (prefixed as specified with df-) located in the corresponding namespace, contains the state, and also holds any static methods. This is a lot of detail to contend with, but understanding it’s important when arranging your Clojure projects to take advantage of code compilation.

One final important point when using gen-class is the semantics surrounding the :impl-ns directive. Our example relies on the fact that the gen-class namespace is the same as the implementation namespace (the :impl-ns), meaning that the compilation will transitively compile all of the implementation functions. On the other hand, when your implementation and gen-class namespaces are distinct, you no longer suffer transitive compilation. This provides the benefit of allowing a mixture of compiled (class files) and uncompiled (.clj files) Clojure products.

10.2.2. Exploring user interface design and development with Clojure

Before we begin, we’ll devise a simple model (_why 2007[3]) for exploring user interface design. We don’t have to complicate matters, because the goal is only to get a general idea of how Clojure makes a typically painful task like Java GUI development a joy. To achieve this modest goal, we’ll need some simple containers illustrated in figure 10.4: shelves, stacks, and splitters.

3 Our GUI model in this section is based loosely on the Ruby framework Shoes created by _why. Thank you sir, wherever you are.

Figure 10.4. Basic GUI containers: using only a handful of rudimentary containers, we can build neato GUI prototypes.

Because DynaFrame requires a java.awt.Container as its displayed element, we’ll make each container a derivative thereof. This allows the containers to nest, helping to build richer GUIs. Finally, their forms should mirror their graphical layout, within reason. These three containers are implemented in the following listing.

Listing 10.4. Simple GUI containers
(ns joy.gui.socks
  (:import
   (joy.gui DynaFrame)
   (javax.swing Box BoxLayout JTextField JPanel
                JSplitPane JLabel JButton
                JOptionPane)
   (java.awt BorderLayout Component GridLayout FlowLayout)
   (java.awt.event ActionListener)))

(defn shelf [& components]
  (let [shelf (JPanel.)]
     (.setLayout shelf (FlowLayout.))
     (doseq [c components] (.add shelf c))
     shelf))

(defn stack [& components]
  (let [stack (Box. BoxLayout/PAGE_AXIS)]
     (doseq [c components]
       (.setAlignmentX c Component/CENTER_ALIGNMENT)
       (.add stack c))
     stack))

  (defn splitter [top bottom]
   (doto (JSplitPane.)
     (.setOrientation JSplitPane/VERTICAL_SPLIT)
     (.setLeftComponent top)
     (.setRightComponent bottom)))

These simple GUI elements are built on top of the Java Swing library, where each sub-widget in the components argument is added to the properly configured Container-derived parent. These are good as a starting point, but still there’s nothing to display unless we dive into the Swing API directly. We can do one better than that by providing a simple base set of widgets: buttons, labels, and text boxes.

Listing 10.5. A set of simple widgets
(defn button [text f]
  (doto (JButton. text)
    (.addActionListener
     (proxy [ActionListener] []
       (actionPerformed [_] (f))))))

(defn txt  [cols t]
  (doto (JTextField.)
    (.setColumns cols)
     (.setText t)))

(defn label [txt] (JLabel. txt))

The button element takes a function executed on a mouse-click, so we’ll now provide a JavaScript-like alert function as a simple action:

(defn alert
  ([msg] (alert nil msg))
  ([frame msg]
     (javax.swing.JOptionPane/showMessageDialog frame msg)))

Having built all of these GUI elements, we’ll describe the first simple GUI as shown in figure 10.5.

Figure 10.5. DynaFrame alerts: we can create slightly more complex GUIs and attach actions on the fly.

It seems simple, if not pointless. But you might be pleasantly surprised with the concise code used to describe it:

(.display gui
  (splitter
    (button "Procrastinate" #(alert "Eat Cheetos"))genclass
    (button "Move It" #(alert "Couch to 5k"))))

These widgets are adequate enough to create richer user interfaces, and to illustrate we’ll add one more widget builder for grid-like elements:

 (defn grid [x y f]
   (let [g (doto (JPanel.)
             (.setLayout (GridLayout. x y)))]
      (dotimes [i x]
        (dotimes [j y]
         (.add g (f))))
      g))

With a small amount of code, we can build the richer user interface in figure 10.6.

Figure 10.6. A much more elaborate DynaFrame GUI: there’s no limit to the complexity of this simple GUI model. Go ahead and experiment to your heart’s content.

Listing 10.6. A more complex GUI example
 (.display gui
   (let [g1 (txt 10 "Charlemagne")
         g2 (txt 10 "Pippin")
          r  (txt 3 "10")
          d  (txt 3 "5")]
      (splitter
        (stack
          (shelf (label "Player 1") g1)
          (shelf (label "Player 2") g2)
          (shelf (label "Rounds ") r
                 (label "Delay  ") d))
        (stack
          (grid 21 11 #(label "-"))
          (button "Go!" #(alert (str (.getText g1) " vs. "
                                     (.getText g2) " for "
                                     (.getText r)  " rounds, every "
                                     (.getText d)  " seconds.")))))))

Though not perfect, it gives you a good idea how to extend these functions to provide a finer level of control over layout and positioning, as well as ways to provide more functionality to create richer interfaces. How would you go about creating an agile environment for incremental GUI development using plain Java? Clojure allows you to start with a powerful set of primitives and incrementally refine them until they suit your exact needs.

Though this section started as a description of creating a simple dynamic frame using the gen-class facility, we felt it was worthwhile to expand into the realm of dynamic, incremental development. There are times when AOT compilation is absolutely necessary (such as client requirements), but our advice is to avoid it if at all possible. Instead, leverage the dynamic nature of Clojure to its fullest, designing your system to fit into that model.

10.3. Clojure’s relationship to Java arrays

In general, the need to delve into arrays should be limited, but such casual dismissal isn’t always apropos. In this section, we’ll cover some of the uses for Java arrays in Clojure, including but not limited to arrays as multimethod dispatch, primitive versus reference arrays, calling variadic functions and constructors, and multi-dimensional arrays.

10.3.1. Types of arrays: primitive and reference

As mentioned in section 4.1, Clojure numbers are of the boxed variety, but in many cases the Clojure compiler can resolve the correct call for primitive interoperability calls. But it can never resolve the need to pass a primitive array when a reference array is provided instead.

Creating Primitive Arrays

The Java class java.lang.StringBuilder provides[4] a method .append(char[]) that appends the primitive chars in the passed array to its end. But our first instinct for making this happen in Clojure won’t bear fruit:

4 When dealing with and manipulating strings, your best options can almost always be found in the core clojure.string namespace or the clojure.contrib.string namespace in the Clojure contrib library.

(doto (StringBuilder. "abc")
  (.append (into-array [x y z])))

;=> #<StringBuilder abc[Ljava.lang.Character;@65efb4be>

The problem lies in that Clojure’s into-array function doesn’t return a primitive array of char[], but instead a reference array of Character[], forcing the Clojure compiler to resolve the call as to the StringBuilder.append(Object) method instead. That the Array class is a subclass of Object is a constant cause for headache in Java and clearly can be a problem[5] for Clojure as well. What we really want to do is ensure that a primitive array is used as the argument to .append, which we do here:

5 In this example, it’s preferred that a “java.lang.IllegalArgumentException: No matching method found” exception be thrown, because StringBuilder doesn’t have a method matching .append(Character[]) or even .append(Object[]).

(doto (StringBuilder. "abc")
  (.append (char-array [x y z])))

;=> #<StringBuilder abcxyz>

Clojure provides a number of primitive array-building functions that work similarly to char-array, as summarized in the following list.

  • boolean-array
  • byte-array
  • char-array
  • double-array
  • float-array
  • int-array
  • long-array
  • object-array
  • short-array

You could also use the make-array and into-array functions to create primitive arrays:

(let [ary (make-array Integer/TYPE 3 3)]
  (dotimes [i 3]
    (dotimes [j 3]
      (aset ary i j (+ i j))))
  (map seq ary))

;=> ((0 1 2) (1 2 3) (2 3 4))

(into-array Integer/TYPE [1 2 3])
;=> #<int[] [I@391be9d4>

Populating arrays can often be an iterative affair, as seen in the previous snippet, but there are often more concise ways to do so when creating reference arrays.

Creating Reference Arrays

To intentionally create an array of a particular reference type, or of compatible types, use the into-array function, passing in a sequence of objects:

(into-array ["a" "b" "c"])
;=> #<String[] [Ljava.lang.String;@3c3ac93e>

(into-array [(java.util.Date.) (java.sql.Time. 0)])
;=> #<Date[] [Ljava.util.Date;@178aab40>

(into-array ["a" "b" 1M])
; java.lang.IllegalArgumentException: array element type mismatch

(into-array Number [1 2.0 3M 4/5])
;=> #<Number[] [Ljava.lang.Number;@140b6e46>

The function into-array determines the type of the resulting array based on the first element of the sequence, and each subsequent element type must be compatible (a subclass). To create a heterogeneous array of java.lang.Object, use the to-array or to-array-2d function:

to-array-2d function:

(to-array-2d [[1 2 3]
              [4 5 6]])
;=> #<Object[][] [[Ljava.lang.Object;@bdccedd>

(to-array ["a" 1M #(%) (proxy [Object] [])])
;=> #<Object[] [Ljava.lang.Object;@18987a33>

(to-array [1 (int 2)])
;=> #<Object[] [Ljava.lang.Object;@6ad3c65d>

Be wary: primitives will be autoboxed when using either to-array or to-array-2d.

10.3.2. Array mutability

Because JVM arrays are mutable, you need to be aware that their contents can change at any point. For example:

(def ary  (into-array [1 2 3]))
(def sary (seq ary))
sary
;=> (1 2 3)

What happens to sary if we change the contents of ary?

(aset ary 0 42)
sary
;=> (42 2 3)

The seq view of an array is that of the live array and therefore subject to concurrent modification. Be cautious when sharing arrays from one function to the next, and especially across threads. Note that this can be especially disastrous should an array change in the middle of a sequential operation, such as the use of the higher-order array functions amap and areduce, as might be used to define a sum-of-squares function[6] for arrays:

6 This function is fairly clear but slower than it should be. We’ll make it faster in sections 12.1 and 12.5.

(defn asum-sq [xs]
  (let [dbl (amap xs i ret
              (* (aget xs i)
                 (aget xs i)))]
    (areduce dbl i ret 0
      (+ ret (aget dbl i)))))

(asum-sq (float-array [1 2 3 4 5]))
;=> 55.0

At any point during the processing of asum-sq, the underlying array could change, causing inaccurate results or worse. You should take great care when using Java’s mutable arrays, though sharing only the seq of an array is perfectly safe because there’s no way to get at the array when you only have a reference to the seq.

10.3.3. That unfortunate naming convention

You might’ve noticed (how could you miss?) the ugly names printed by the Clojure REPL whenever an array is evaluated. There’s logic to this madness, as part of the jumble is the legal name of the class corresponding to the array—the part formed as [Ljava.lang.String;. For example, the previous name corresponded to a 1D array of strings. The representation for a 2D array of strings is then [[Ljava.lang.String;, and it therefore follows that [[[Ljava.lang.String; is a 3D array of strings. Are you sensing a pattern here? Table 10.1 lays it out.

Table 10.1. Array type class names and dimensions

Representation

Array type

[Ljava.lang.Object; Reference array
[B Primitive byte array
[I Primitive int array
[C Primitive char array
[S Primitive short array
[F Primitive float array
[D Primitive double array
[J Primitive long array
[Z Primitive boolean array
Representation Dimension
[ 1D
[[ 2D
... and so on...

Using what you know about arrays, the class representation names can be used to do things such as multimethod dispatch:

(what-is (into-array ["a" "b"]))
;=> "1d String"

(what-is (to-array-2d [[1 2][3 4]]))
;=> "2d Object"

(what-is (make-array Integer/TYPE 2 2 2 2))
;=> "Primitive 4d int"

You can create methods for identifying arrays and returning a descriptive string using the <indexterm><primary>java.lang.Class/forName</primary></indexterm>Class/forName method as shown:

(defmulti what-is class)
(defmethod what-is (Class/forName "[Ljava.lang.String;") [a] "1d String")
(defmethod what-is (Class/forName "[[Ljava.lang.Object;") [a] "2d Object")
(defmethod what-is (Class/forName "[[[[I") [a] "Primitive 4d int")

Though not the most beautiful task to perform in Clojure, it’s easy to understand once you’ve grasped how the array class names are constructed.

10.3.4. Multidimensional arrays

Observe what happens when the following call is tried:

(what-is (into-array [[1.0] [2.0]]))
; java.lang.IllegalArgumentException: No method in multimethod
;  'what-is' for dispatch value: class [Lclojure.lang.PersistentVector;

The problem is that the into-array function builds a 1D array of persistent vectors, but we wanted a 2D array of doubles. In order to do this, the array would have to be built differently:

(defmethod what-is (Class/forName "[[D") [a] "Primitive 2d double")
(defmethod what-is (Class/forName "[Lclojure.lang.PersistentVector;") [a]
  "1d Persistent Vector")

(what-is (into-array (map double-array [[1.0] [2.0]])))
;=> "Primitive 2d double"

(what-is (into-array [[1.0] [2.0]]))
;=> "1d Persistent Vector"

We had to use the map function with double-array on the inner arrays in order to build the properly typed outer array. When working with multidimensional arrays, be sure that you know what your inner elements should be on creation and create them accordingly.

10.3.5. Variadic method/constructor calls

There’s no such thing as a variadic constructor or method at the bytecode level, although Java provides syntactic sugar at the language level. Instead, variadic methods expect an array as their final argument, and this is how they should be accessed in Clojure interop scenarios. Take, for example, the call to the String/format function:

(String/format "An int %d and a String %s" (to-array [99, "luftballons"]))
;=> "An int 99 and a String luftballons"

That covers most of the high points regarding arrays in Clojure interoperability. We’ll touch on them briefly when we talk about performance considerations in chapter 12, but for now we’ll move on to a more interesting topic: the interoperability underpinnings relating to Clojure’s implementation.

10.4. All Clojure functions implement...

Clojure functions are highly amenable to interoperability. Their underlying classes implement a number of useful interfaces that you can investigate by running (ancestors (class #())). Most of the resulting classes are only applicable to the internals of Clojure itself, but a few interfaces are useful in interop scenarios: java.util.concurrent.Callable, java.util.Comparator, and java.lang.Runnable. In this section, we’ll talk briefly about each and also provide simple examples.

10.4.1. java.util.Comparator

Simply put, the java.util.Comparator interface defines the signature for a single method .compare that takes two objects l and r and returns -1 if l < r, 0 if l == r, and > 0 if l > r. The static Java method Collections/sort provides an implementation that takes a derivative of java.util.List and a Comparator and destructively sorts the list provided. Using this knowledge, we can provide some basic infrastructure for the remainder of this subsection:

(import '[java.util Comparator Collections ArrayList])

(defn gimme [] (ArrayList. [1 3 4 8 2]))

(doto (gimme)
  (Collections/sort (Collections/reverseOrder)))
;=> #<ArrayList [8, 4, 3, 2, 1]>

In order to write a simple comparator that provides a reverse-sort Comparator, we might naively do so:

(doto (gimme)
  (Collections/sort
    (reify Comparator
      (compare [this l r]
        (cond
          (> l r) -1
          (= l r) 0
          :else 1)))))
;=> #<ArrayList [8, 4, 3, 2, 1]>

Though this works, Clojure provides a better way by allowing the use of a function as the Comparator directly. You can couple this knowledge with the fact that Clojure already provides numerous functions useful for comparison, as shown next.

Listing 10.7. Useful comparison functions

When presented with numerous possible implementation strategies, often the best one in Clojure is the simplest.

10.4.2. java.lang.Runnable

Java threads expect an object implementing the java.lang.Runnable interface meant for computations returning no value. We won’t get into the specifics of threaded computation until the next chapter, but the next two examples are simple enough to require little a priori knowledge on the matter. If you wish to pass a function to another Java thread, it’s as simple as providing it as an argument to the Thread constructor:

(doto (Thread. #(do (Thread/sleep 5000)
                    (println "haikeeba!")))
  .start)
; => #<Thread Thread[Thread-3,5,main]>
; ... 5 seconds later
; haikeeba!

This scenario is unlikely to occur often, because Clojure’s core concurrency features are often sufficient for most needs. But that’s not always the case, and therefore it’s nice to know that raw Clojure functions can be used seamlessly in the JVM’s concurrency API.

10.4.3. java.util.concurrent.Callable

The Java interface java.util.concurrent.Callable is specifically meant to be used in a threaded context for computations returning a value. You can use a Clojure function using Java’s java.util.concurrentFutureTask class representing a “computation to occur later”:

(import '[java.util.concurrent FutureTask])

(let [f (FutureTask. #(do (Thread/sleep 5000) 42))]
  (.start (Thread. #(.run f)))
  (.get f))
; ... 5 seconds later
;=> 42

The call to FutureTask.get as the last expression will stop execution (a behavior known as blocking) until the function passed to the constructor completes. Because the function in question sleeps for 5 seconds, the call to .get must wait.

Clojure’s interoperability mechanisms are a two-way street. Not only do they allow Java APIs to work seamlessly within Clojure, but they also provide ways for Clojure functions to work in Java APIs. In the next section, we’ll continue on this theme of bidirectional interop with a discussion on the ways that Clojure’s collection types can also be used in traditional Java APIs.

10.5. Using Clojure data structures in Java APIs

Clojure functions are ready to use in many Java APIs, and as it turns out, so are its collection types. Just as the Clojure collections are separated along three distinct equality partitions[7] (maps, sequences, and sets), so too are its levels of Java collection interoperability support. The Java Collections Framework has a nice high-level design philosophy centered around working against interfaces. These interfaces are additionally cognizant of immutability, in that the mutable parts are optional and the immutable parts are clearly demarcated. In this section, we’ll give a brief rundown of possible ways that Clojure collections can be used within traditional Java APIs adhering to the immutable collection protocols.

7 A refresher on equality partitions can be found in section 5.1.2 and throughout the remainder of chapter 5.

10.5.1. java.util.List

Clojure sequential collections conform to the immutable parts of the java.util.List interface, which in turn extends the java.util.Collection and java.lang.Iterable interfaces. You can see this conformance in action in the following listing.

Listing 10.8. java.util.List conformance for sequences and seqs

That Clojure sequences and seqs don’t provide the mutable API of typical Java collections is obvious. But the implications are that you can’t use them in all Java APIs, such as you might attempt when requiring that a vector be sorted destructively with a Java API call:

(java.util.Collections/sort [3 4 2 1])
; java.lang.UnsupportedOperationException

A better approach is to either use the method used in the previous section using a Clojure function, or even better to use the Clojure’s sort function instead.

10.5.2. java.lang.Comparable

The interface java.lang.Comparable is the cousin of the Comparator interface. Comparator refers to objects that can compare two other objects, whereas Comparable refers to an object that can compare itself to another object:

(.compareTo [:a] [:a])
;=> 0

(.compareTo [:a :b] [:a])
;=> 1

(.compareTo [:a :b] [:a :b :c])
;=> -1

(sort [[:a :b :c] [:a] [:a :b]])
;=> ([:a] [:a :b] [:a :b :c])

One thing to note is that Clojure’s vector implementation is currently the only collection type that implements the java.lang.Comparable interface providing the .compareTo method. As a result, attempting to compare a different collection type to a vector leads to a confusing error message:

(.compareTo [1 2 3] '(1 2 3))

; java.lang.ClassCastException: clojure.lang.PersistentList
;    cannot be cast to clojure.lang.IPersistentVector

Pay no attention to that class-cast exception behind the curtain.

10.5.3. java.util.RandomAccess

In general, the java.util.RandomAccess interface is used to indicate that the data type provides constant time indexed access to its elements. This allows for algorithms to follow optimized paths accordingly. This optimization is generally performed by using the .get method for access rather than an iterator:

(.get '[a b c] 2)
;=> c

Vectors are currently the only Clojure collection type that can make such guarantees.

10.5.4. java.util.Collection

The java.util.Collection interface lies at the heart of the Java Collections Framework, and classes implementing it can play in many of Java’s core collections APIs. A useful idiom taking advantage of this fact is the use of a Clojure sequence as a model to build a mutable sequence for use in the Java Collections API, as shown:

(defn shuffle [coll]
  (seq (doto (java.util.ArrayList. coll)
         java.util.Collections/shuffle)))

(shuffle (range 10))
;=> (3 9 2 5 4 7 8 6 1 0)

It’s difficult to write a proper sequence-shuffling function, so the shuffle function takes full advantage of an existing Java API that has been tested and used extensively for years. As an added bonus, shuffle is mostly[8] functional, idiomatic, and fast. Clojure favors immutability but doesn’t trap you into it when there are practical solutions to be leveraged.

8shuffle isn’t referentially transparent. Can you see why?

Java.Util.Map

Like most of the Clojure collections, its maps are analogous to Java maps in that they can be used in nonmutating contexts. But immutable maps have the added advantage of never requiring defensive copies and will act exactly the same as unmodifiable Java maps:

(java.util.Collections/unmodifiableMap
  (doto (java.util.HashMap.) (.put :a 1)))
;=> #<UnmodifiableMap {:a=1}>
(into {} (doto (java.util.HashMap.) (.put :a 1)))
;=> {:a 1}

In both cases, any attempt to modify the map entry classes of the maps will throw an exception.

10.5.5. java.util.Set

In the case of Java and Clojure sets, the use of mutable objects[9] as elements is highly frowned upon:

9 Clojure’s mutable reference types used to represent a logical identity are perfectly safe to use in sets. We’ll explore the reference types in exquisite detail in the next chapter.

(def x (java.awt.Point. 0 0))
(def y (java.awt.Point. 0 42))
(def points #{x y})
points
;=> #{#<Point java.awt.Point[x=0,y=0]> #<Point java.awt.Point[x=0,y=42]>}

Everything looks peachy at this point, but introducing mutability into the equation has devastating costs:

(.setLocation y 0 0)
points
;=> #{#<Point java.awt.Point[x=0,y=0]> #<Point java.awt.Point[x=0,y=0]>}

Oh boy. Not only have we confused the set points by modifying its entries out from underneath it, but we’ve also circumvented Clojure’s value-based semantics and the nature of set-ness. Dealing with mutable objects is extremely difficult to reason about, especially when dealing with collections of them. The gates of a mutable class are wide open, and at any point during the execution of your programs this fact can be exploited, willingly or not. But you can’t always avoid dealing with mutable nasties in Clojure code because of a strict adherence to fostering interoperability.

We’ve covered the two-way interop for functions and now collection types, but we have one final path to traverse: the use and benefits of Clojure’s definterface macro.

10.6. definterface

As we mentioned in section 9.3, Clojure was built on abstractions in the host platform Java. Types and protocols help to provide a foundation for defining your own abstractions in Clojure itself, for use within a Clojure context. But when interoperating with Java code, protocols and types won’t always suffice. Therefore, you need to be able to generate interfaces in some interop scenarios, and also for performance in cases involving primitive argument and return types. In this section, we’ll talk briefly about generating Java interfaces as the syntax, use cases, and purposes are likely familiar.

10.6.1. Generating interfaces on the fly

When you AOT-compile a protocol, you generate a public interface by the same name, with the methods defined. The code in listing 10.9 uses definterface to define an interface ISliceable. This interface is used to define an abstract thing that has the ability to be sliced using a method slice, which takes start and end indices of type int. Likewise, the interface defines a method sliceCount that returns an int representing the number of possible slices.

Listing 10.9. An interface defining a sliceable object
(definterface ISliceable
  (slice [^int s ^int e])
  (^int sliceCount []))
;=> user.ISliceable

You’ll notice the inclusion of the type decoration ^int on the arguments to slice and the return type of sliceCount. For now you can assume that they operate the same as a type declaration in most languages providing them. They look similar to type hints discussed in section 12.1, except that only in definterface are primitive hints supported. Now we can create an instance implementing the user.ISliceable interface, as shown next.

Listing 10.10. A dummy reified ISliceable
(def dumb
  (reify user.ISliceable
    (slice [_ s e] [:empty])
    (sliceCount [_] 42)))

(.slice dumb 1 2)
;=> [:empty]

(.sliceCount dumb)
;=> 42

There’s nothing terribly surprising about dumb, but you can instead implement it via deftype, proxy, gen-class, or even a Java class. Note that definterface works even without AOT compilation.

We can now take definterface to the next logical step and extend the ISliceable interface to other types using a well-placed protocol.

Listing 10.11. Using a protocol to extend ISliceable
(defprotocol Sliceable
  (slice [this s e])
  (sliceCount [this]))

(extend user.ISliceable
  Sliceable
  {:slice (fn [this s e] (.slice this s e))
   :sliceCount (fn [this] (.sliceCount this))})

(sliceCount dumb)
;=> 42

(slice dumb 0 0)
;=> [:empty]

By extending the ISliceable interface along Sliceable, ISliceable is able to participate in the protocol, meaning that you have the possibility for extending other types, even final types such as String, as shown next.

Listing 10.12. Extending strings along the Sliceable protocol
(defn calc-slice-count [thing]
  "Calculates the number of possible slices using the formula:
     (n + r - 1)!
     ------------
      r!(n - 1)!
   where n is (count thing) and r is 2"
  (let [! #(reduce * (take % (iterate inc 1)))
        n (count thing)]
    (/ (! (- (+ n 2)  1))
       (* (! 2) (! (- n 1))))))

(extend-type String
  Sliceable
  (slice [this s e] (.substring this s (inc e)))
  (sliceCount [this] (calc-slice-count this)))

(slice "abc" 0 1)
;=> "ab"
(sliceCount "abc")
;=> 6

The advantages of using definterface over defprotocol are restricted entirely to the fact that the former allows primitive types for arguments and returns. At some point in the future, the same advantages will likely be extended to the interfaces generated, so use definterface sparingly and prefer protocols unless absolutely necessary.

10.7. Be wary of exceptions

There’s been much debate on the virtues of checked exceptions in Java, so we won’t cover that here. Instead, we’ll stick to the facts regarding the nuances the JVM imposes on Clojure’s error-handling facilities. Before we begin, consider the following view on the use of exceptions in Clojure source:

When writing Clojure code, use errors to mean can’t continue and exceptions to mean can or might continue.

We’ll attempt to constrain ourselves to the generalities of exception handling in this section. If you desire information on deciphering exception messages, we talked about that in section 3.4. If you’re curious about the effects of exceptions on continuation-passing style, then refer back to section 7.3.4. We discussed the behavior of Clojure to attempt to supplant numerical inaccuracies by throwing exceptions in section 4.1.3. If you instead want to learn about the interplay between exceptions and Clojure’s reference types, then such matters can be found throughout chapter 11. Finally, if you have no idea what an exception is, then we discuss the basics in section 1.5.8.

10.7.1. A bit of background regarding exceptions

The behavior of Clojure’s exception features directly spawns from the JVM enforcing the promulgation of checked exceptions. Virtuous or not in the context of Java development, checked exceptions are antithetical to closures and higher-order functions. Checked exceptions require that not only should the thrower and the party responsible for handling them declare interest, but every intermediary is also forced to participate. These intermediaries don’t have to actively throw or handle exceptions occurring within, but they must declare that they’ll be “passing through.” Therefore, by including the call to a Java method throwing a checked exception within a closure, Clojure has two possible alternatives:

  • Provide a cumbersome exception declaration mechanism on every single function, including closures.
  • By default, declare that all functions throw the root Exception or Runtime-Exception.

And as you can probably guess, Clojure takes the second approach, which leads to a condition of multilevel wrapping of exceptions as they pass back up the call stack. This is why you see, in almost any (.printStackTrace *e) invocation, the point of origin of an error offset by some number of layers of java.lang.RuntimeException. Because Java interfaces and classes get to decide what types of problems potential derivative classes and even callers can have, Clojure needs to handle the base java.lang.Exception at every level, because it has to preserve dynamism in the face of a closed system. Unless you’re directly calling something that throws typed exceptions, your best bet is to catch Exception and then see what you have in context.

10.7.2. Runtime versus compile-time exceptions

There are two contexts in Clojure where exceptions can be thrown: runtime and compile time. In this section we’ll touch on both, explaining how and when to use them.

Runtime Exceptions

The case of runtime exceptions might be the most familiar, because it’s likely to have been encountered and utilized in your own code. There are two types of runtime exceptions: errors and exceptions. We can illustrate the difference between the two by showing you the following:

(defn explode [] (explode))
(try (explode) (catch Exception e "Stack is blown"))
; java.lang.StackOverflowError

So why were we unable to catch the java.lang.StackOverflowError? The reason lies in Java’s exception class hierarchy and the fact that StackOverflowError isn’t a derivative of the Exception class, but instead of the Error class:

(try (explode) (catch StackOverflowError e "Stack is blown"))
;=> "Stack is blown"
(try (explode) (catch Error e "Stack is blown"))
;=> "Stack is blown"

(try (explode) (catch Throwable e "Stack is blown"))
;=> "Stack is blown"

(try (throw (RuntimeException.))
  (catch Throwable e "Catching Throwable is Bad"))
;=> "Catching Throwable is Bad"

We started with a catch of the most specific exception type StackOverflowError and gradually decreased specificity until catching Throwable, which as you’ll notice also catches a RuntimeException. In Java, catching exceptions at the level of Throwable is considered bad form, and it should generally be viewed the same in Clojure. Therefore, we suggest that you follow the advice stated in the opening to this section and reserve those deriving from Errors for conditions that can’t be continued from and those from Exception indicating possible continuation.

Compile-Time Exceptions

There are a few ways that you might come across compile-time exceptions, the most obvious occurring within the body of a macro:

(defmacro do-something [x] `(~x))
(do-something 1)
; java.lang.ClassCastException:
;   java.lang.Integer cannot be cast to clojure.lang.IFn

Though the type of the exception is a java.lang.ClassCastException, it was indeed thrown by the compiler, which you’d see if you were to trace the stack using something like (for [e (.getStackTrace *e)] (.getClassName e)).[10] It’s perfectly acceptable (and even encouraged) to throw exceptions within your own macros, but it’s important to make a distinction between a compile-time and runtime exception.

10 This is a limited analogy to Groovy’s .? operator. Clojure also provides convenience functions for displaying and handling stack traces in the clojure.stacktrace namespace.

 

Compile-Time Exceptions

Why delay until runtime the reporting of an error that at compile time you know exists?

 

The way to throw a compile-time exception is to make sure your throw doesn’t occur within a syntax-quoted form, as we show in the following listing.

Listing 10.13. Throwing a compile-time exception
(defmacro pairs [& args]
  (if (even? (count args))
   `(partition 2 '~args)
    (throw (Exception. (str "pairs requires an even number of args")))))

(pairs 1 2 3)
; java.lang.Exception: pairs requires an even number of args

(pairs 1 2 3 4)
;=> ((1 2) (3 4))

Nothing is preventing the exception from being thrown at runtime, but because we know that pairs requires an even number of arguments, we instead prefer to fail as early as possible—at compilation time. This difference is clearly demonstrated by repeating the preceding test in a function definition:

(fn [] (pairs 1 2 3))
; java.lang.Exception: pairs requires an even number of args

A runtime exception wouldn’t have been thrown until this function was called, but because the pairs macro threw an exception at compile time, users are notified of their error immediately. Though powerful, you should always try to balance the benefits of compile-time error checking with macros and the advantages that implementing as a function provides (the use in higher-order functions, apply, and so on).

10.7.3. Handling exceptions

There are two ways to handle exceptions and errors, each defined by the way in which the error-handling mechanisms “flow” through the source. Imagine that you want a macro that provides a limited[11] null-safe (Koenig 2007) arrow that catches any occurrence of a NullPointerException in a pipeline:

11 There are much more comprehensive -?> and .?. macros found in the clojure.contrib.core namespace, and those are recommended above the one in this section.

(defmacro -?> [& forms]
 `(try (-> ~@forms)
    (catch NullPointerException _# nil)))

(-?> 25 Math/sqrt (+ 100))
;=> 105.0

(-?> 25 Math/sqrt (and nil) (+ 100))
;=> nil

The flow of any occurrence of NullPointerException happens from the inner functions of the stitched forms. Conceptually, this flow can be viewed as in figure 10.7, which describes the way that errors can be caught depending on the direction in which data is moving along the stack.

Figure 10.7. Outside-in and inside-out error handling. There are two ways to handle errors in Clojure. The typical way is to let exceptions flow from the inner forms to the outer. The other way, discussed in section 13.4, uses dynamic bindings to “reach into” the inner forms to handle them immediately.

The typical (try ... (catch ...)) form would therefore be used for the case where the handler catches errors bubbling outward from inner functions and forms, as seen in the -?> macro. But if you want to catch errors at their point of origin, you’ll need a way to pass handlers up the stack. Fortunately, Clojure provides a way to do this via its dynamic Var feature, which will be discussed in section 13.5.

10.7.4. Custom exceptions

If you’re inclined to write your own exception and error types, then you’ll need to do so using the gen-class feature described in section 10.2. JVM exceptions again are a closed system, and it might be better to explore other possibilities (Houser EK) for reporting and handling errors in your Clojure code. But, should you wish to ignore this advice, then bear in mind that it’s rare for Clojure core functions to throw exceptions, and even more rarely are they checked exceptions. The idiom is for Clojure to throw derivatives of RuntimeException or Error, and thus your code should also strive for this when appropriate.

10.8. Summary

Clojure provides an extensive set of data abstractions via its types and protocols. It also provides an extensive interoperability facility through proxy, gen-class, definterface, exception handling, and the implementation of core Java collection interfaces. Though we stress that types and protocols will give you the performant abstractions needed for solving most problems, we realize that not all interop scenarios are solved this way. In these circumstances, you should use the features listed in this chapter to push you the remainder of the way toward your solution. Clojure embraces Java interoperability, but it does so in specific ways, and with a specific set of tools.

In the next chapter, we move on to a rather complex topic, and one that Clojure helps to simplify—shared state concurrency and mutation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.92.199