Off you go

Clojure has really good integration with the java platform. It’s really easy to instantiate java classes and call their methods. Another very useful Clojure construct is the proxy macro which allows the easy construction of a Clojure object that can be called from Java. The constructed proxy can inherit from an existing class and can implement a set of interfaces. Methods can be defined to override those of any base class, and interface methods may be implemented too. By default, interface methods default to throwing an UnsupportedOperationException.

Clojure functions are based on the AFunction abstract class which defines invoke methods that throw an exception representing a wrong number of arguments, with the intention that a user function will implement the overload which has the appropriate number of arguments.

Hence we could define an instance inheriting from this abstract class and then call it.
user=> ((proxy [clojure.lang.AFunction] []))
java.lang.IllegalArgumentException: Wrong number of args passed to: AFunction (NO_SOURCE_FILE:0)

We can start defining some of the overloaded methods.
user=> ((proxy [clojure.lang.AFunction] []
           (invoke [x]
           (println "Called with " x)
           123))
        20)
Called with  20
123

And can define multiple overloads at the same time.
user=> ((proxy [clojure.lang.AFunction] []
           (invoke
            ([x]
               (println "Called with " x)
               47)
            ([ x y]
               (println "Called with two args" x y) 33)))
        20)
Called with  20
47

user=> ((proxy [clojure.lang.AFunction] []
           (invoke
            ([x]
               (println "Called with " x)
               47)
            ([ x y]
               (println "Called with two args" x y) 33)))
        20  30)
Called with two args 20 30
33

A this variable is bound during the execution of the method, allowing us to call back into the proxy object.
user=> ((proxy [clojure.lang.AFunction] []
           (invoke
            ([x]
               (println "Called with " x)
               47)
            ([ x y]
               (println "Called with two args" x y)
               (. this (invoke x))
               (. this (invoke y)))))
        1 2)
Called with two args 1 2
Called with  1
Called with  2
47

It’s really neat that we can use this to check which underlying methods are called by the Clojure functions.
user=> (next (proxy [clojure.lang.ISeq] []
                (next [] (list 30))))
(30)

There is a related macro, gen-class, that can be used to generate a loadable class which can potentially load Clojure (the runtime) and even define a main method so that the class can be used to start an application. gen-class can be added into the expansion of the ns macro if :gen-class arguments are provided. The usual expansion for gen-class uses the *compile-files* Var to only do work if we are in the middle of compiling something. The body of the expansion uses a function, generate-class, to generate the bytecode for a class which is then written using the clojure.lang.Compiler/writeClassFile function.

generate-class takes many options. We could use it as below to generate a class file, test/bar.class on the class path.

clojure.core=> (generate-class {:name ‘test.bar :init ‘startup :main true})
["test/bar" #<byte[] [B@1c64ed8>]
clojure.core=> (let [[cname bytecode] *1]
                  (clojure.lang.Compiler/writeClassFile cname bytecode))
nil

The generated class has the following definition.

public class bar
{
  private static final Var startup__var = Var.internPrivate("clojure.core", "-startup");
  private static final Var main__var = Var.internPrivate("clojure.core", "-main");
  private static final Var hashCode__var = Var.internPrivate("clojure.core", "-hashCode");
  private static final Var clone__var = Var.internPrivate("clojure.core", "-clone");
  private static final Var equals__var = Var.internPrivate("clojure.core", "-equals");
  private static final Var toString__var = Var.internPrivate("clojure.core", "-toString");

  static
  {
    RT.var("clojure.core", "load").invoke("/clojure/core");
  }

  public String toString() …
  public boolean equals(Object paramObject) …
  public Object clone() …
  public int hashCode() …
  public static void main(String[] paramArrayOfString)…
}

As you can see, a prefix is added to the method names when constructing the Clojure names. These are searched for in a namespace which defaults to one named after the class file. The main function has the following definition:
public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #29; //Field main__var:Lclojure/lang/Var;
   3:   dup
   4:   invokevirtual   #65; //Method clojure/lang/Var.isBound:()Z
   7:   ifeq    16
   10:  invokevirtual   #69; //Method clojure/lang/Var.get:()Ljava/lang/Object;
   13:  goto    18
   16:  pop
   17:  aconst_null
   18:  dup
   19:  ifnull  38
   22:  checkcast       #56; //class clojure/lang/IFn
   25:  aload_0
   26:  invokestatic    #120; //Method clojure/lang/RT.seq:(Ljava/lang/Object;)Lclojure/lang/ISeq;
   29:  invokeinterface #124,  2; //InterfaceMethod clojure/lang/IFn.applyTo:(Lclojure/lang/ISeq;)Ljava/lang/Object;
   34:  pop
   35:  goto    48
   38:  new     #79; //class java/lang/UnsupportedOperationException
   41:  dup
   42:  ldc     #126; //String clojure.core/-main not defined
   44:  invokespecial   #84; //Method java/lang/UnsupportedOperationException."<
init>":(Ljava/lang/String;)V
   47:  athrow
   48:  return

We can put all of this together to get a very simple application. In a file helloworld.clj, we can define a namespace and a main method.

(ns test.helloworld
    (:gen-class))

(defn -main[]
  (println "Hello World!"))

We can then compile this using the compile function.

user=> (compile ‘test.helloworld)
test.helloworld

We get the following classes generated

30/01/2010  14:05               914 helloworld$_main__339.class
30/01/2010  14:05             1,783 helloworld.class
30/01/2010  14:05             2,260 helloworld__init.class

And finally, we can run the resulting classes.

C:UsersCliveDesktopclojure-1.0.0>java -cp classes;src/clj test.helloworld
Hello World!

We can push this a bit further to generate a windowing application.

(ns test.helloworld
    (:gen-class)
    (:import (javax.swing JButton JPanel JFrame)))

(defn start-app []
  (let [button (JButton. "Press me!")
        panel (JPanel.)
        frame (JFrame. "Hello App")]
    (. button addActionListener
      (proxy [java.awt.event.ActionListener] []
        (actionPerformed [event]
           (println "Don’t press that again!"))))
    (. panel setOpaque true)
    (. panel add button)
    (. frame setContentPane panel)
    (. frame setSize 300 100)
    (. frame setVisible true)))

(defn -main[]
  (println "Hello World!")
  (start-app))

We can test this at the REPL using

user=> (load-file "src/clj/test/helloworld.clj")
#’test.helloworld/-main
user=> (*1)
Hello World!

and the window appears. We can test things interactively, and reload until we get things working. Finally, we can compile to get a set of classes for the final standalone application. This last step is very neat when compared with the classic style of Common Lisp environment. In those environments, the debugging experience is better as you develop the application inside the Common Lisp environment, but application delivery becomes harder as it is hard to pull the application out of the environment.

user=> (compile ‘test.helloworld)
test.helloworld

30/01/2010  14:30             1,029 helloworld$start_app__176$fn__178.class
30/01/2010  14:30             2,458 helloworld$start_app__176.class
30/01/2010  14:30             1,025 helloworld$_main__183.class
30/01/2010  14:05               914 helloworld$_main__339.class
30/01/2010  14:30             1,783 helloworld.class
30/01/2010  14:30             2,858 helloworld__init.class
               6 File(s)         10,067 bytes

C:UsersCliveDesktopclojure-1.0.0>java -cp classes;src/clj test.helloworld
Hello World !

Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s