A blog about software development and other software related matters

Blog Archive

Friday, October 3, 2008

Closjure it not a spelling mistac ;)

Its seems that these days any programmer has to have a triple set of languages in his tool set, the first one is the static imperative which brings on the food to table (Java or C#), the second one is the dynamic imperative one which is fun and productive to use, it saves the ceremony code (let it be Ruby Python or Groovy) and is useful in configuration code, tests and fast prototypes projects.
As for the third type the magic keyword is functional, learning such a language won't make you code faster or more productive (not from day one at least) instead it will make you smarter and could improve the way in which you solve programing problems (functions all the way baby!!).
The main question here is which one to pick from (Scala, Haskell, ML etc..), i won't go deep into which one you should choose but instead propose a surprising contender called Clojure (a lisp dialect that runs on the JVM), some of the things that appealed to me from the start about Clojure were:

  • Dynamic typing

  • Complete Immutability (has STM and persistent collections) no hidden side effects

  • REPL

  • Macros

  • Java interop

Iv made a bold attempt to write a small clj program which emulates svn blame functionality, the implementation is a trimmed version of an existing svnkit snippet:

import java.io.File;
import java.util.Date;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.util.SVNFormatUtil;
import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNLogClient;
import org.tmatesoft.svn.core.wc.SVNRevision;

public class Annotations {

public static void main(String[] args) {

DAVRepositoryFactory.setup();

try {
SVNURL fileURL = SVNURL.parseURIEncoded("http://gookup.googlecode.com/svn/trunk/gookup-jruby/build/artifacts.rb");
SVNLogClient logClient = SVNClientManager.newInstance().getLogClient();//SVNLogClient is the class with which you can perform annotations
boolean ignoreMimeType = false;
boolean includeMergedRevisions = false;
logClient.doAnnotate(fileURL, SVNRevision.UNDEFINED, SVNRevision.create(1), SVNRevision.HEAD,
ignoreMimeType /*not ignoring mime type*/, includeMergedRevisions /*not including merged revisions */,
new AnnotationHandler(), null);
} catch (SVNException svne) {
System.out.println(svne.getMessage());
System.exit(1);
}
}

private static class AnnotationHandler implements ISVNAnnotateHandler {

/**
* Deprecated.
*/
public void handleLine(Date date, long revision, String author, String line) throws SVNException {
handleLine(date, revision, author, line, null, -1, null, null, 0);
}

/**
* Formats per line information and prints it out to the console.
*/
public void handleLine(Date date, long revision, String author, String line, Date mergedDate,
long mergedRevision, String mergedAuthor, String mergedPath, int lineNumber) throws SVNException {
String revStr = revision >= 0 ? SVNFormatUtil.formatString(Long.toString(revision), 6, false) : " -";
String authorStr = author != null ? SVNFormatUtil.formatString(author, 10, false) : " -";
System.out.println(revStr + " " + authorStr + " " + line);

}

public boolean handleRevision(Date date, long revision, String author, File contents) throws SVNException {
// We do not want our file to be annotated for each revision of the range, but only for the last
// revision of it, so we return false
return false;
}

public void handleEOF() {
}
}
}


The Clojure implementation:
(in-ns 'main)
(clojure/refer 'clojure)
(import '(org.tmatesoft.svn.core SVNException SVNURL)
'(org.tmatesoft.svn.core.wc ISVNAnnotateHandler SVNClientManager SVNLogClient SVNRevision)
'(org.tmatesoft.svn.core.internal.util SVNDate SVNFormatUtil)
'(org.tmatesoft.svn.core.internal.io.dav DAVRepositoryFactory))

(def url "http://gookup.googlecode.com/svn/trunk/gookup-jruby/build/artifacts.rb")

(def format (fn [notEmpty spacing value] (if notEmpty (SVNFormatUtil/formatString (str value) (long spacing) false) " -")))

(def handler (proxy [ISVNAnnotateHandler] []
(handleLine [date revision author line] ())
(handleLine [date revision author line mergedDate mergedRevision mergedAuthor mergedPath lineNumber]
(println (format (>= revision 0) 6 revision)(format (not (nil? author)) 10 author) line))
(handleRevision [date revision author contents] false)
(handleEOF [] ())))

(defn main [args]
(DAVRepositoryFactory/setup)
(let [fileURL (. SVNURL parseURIEncoded url)
logClient (.. SVNClientManager (newInstance) (getLogClient))
revision (SVNRevision/create (long 1))]
(. logClient doAnnotate fileURL SVNRevision/UNDEFINED revision SVNRevision/HEAD false false handler nil))
)

The Clojure implementation is less verbose and demonstrates nicely the how well Java is integrated into the language (note the proxy method that implements the ISVNAnnotateHandler interface), the ".." macro is another nice feature that makes method calls on Java object feel more natural.
Clojure has also function objects (closures), the format closure saves yet more duplication in the handleLine method.
The ugly thing which is common to both implementation is the huge parameters list of svnkit (what were they thinking?).

No comments: