Practical Clojure/Java interop, CSV parsing with Apache Commons CSV

One particularly powerful aspect of Clojure is that it allows you to use Java features without actually writing any Java. And the best way to get my head around the Java interop features is to do a practical example; something like I'd do for work. And as far as examples go, they don't come any more practical than CSV file reading.

Given I'm just learning Clojure, to have any chance of getting the interop code working, I first need to write the Java version. So how would a Java person, which I'm not, parse CSV? Using Apache Commons CSV I suppose. Here's the CSV:

id,greeting
1,Shiver me timbers!
2,Ahoy there!

CSV parsing in Java

Here's the Java code, PiratePhrases.java:

import java.io.FileReader;
import java.io.IOException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

public class PiratePhrases {
    public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader("pirate.csv");
        Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);
        for (CSVRecord record : records) {
            System.out.println(record.get("greeting"));
        }
    }
}

As I'm running Guix SD, I'll install Java and Commons CSV and compile and run it like this:

$ guix package --install icedtea java-commons-csv
$ javac -cp "${HOME}/.guix-profile/share/java/commons-csv.jar" PiratePhrases.java
$ java -cp "${HOME}/.guix-profile/share/java/commons-csv.jar" PiratePhrases
Shiver me timbers!
Ahoy there!

I've barely written an Java since university, and don't use any statically typed languages at the moment so it was comforting to that javac demanded I handle IOExceptions; even if I did fob it off by adding the exception to the interface.

CSV parsing in Clojure

Any sane Clojure programmer would probably reach for a Clojure native CSV library, like data.csv, but since this is an exercise, I'm going to stick with Commons CSV and translate this Java code as literally as I can. The Clojure version, pirate_phrases.clj, looks like this:

(import java.io.FileReader)
(import org.apache.commons.csv.CSVFormat)
(import org.apache.commons.csv.CSVParser)
(import org.apache.commons.csv.CSVRecord)

(let [reader (FileReader. "pirate.csv")
      records (.parse (.withFirstRecordAsHeader CSVFormat/DEFAULT) reader)]
  (doseq [record records]
    (println (.get record "greeting"))))

And, on Guix SD, is run like this:

guix package --install clojure
java -cp "${HOME}/.guix-profile/share/java/clojure-1.9.0.jar:${HOME}/.guix-profile/share/java/commons-csv.jar" clojure.main pirate_phrases.clj
Shiver me timbers!
Ahoy there!

This was a good chance to get familiar with a few of the interop features:

  1. Importing with (import java.io.FileReader)

  2. Instantiating an object, where new FileReader(path) becomes (FileReader. path)

  3. Accessing a class attribute/method, where CSVFormat.DEFAULT becomes CSVFormat/DEFAULT, and

  4. Calling an instance method, where record.get("greeting") becomes (.get record "greeting").

I particularly like that the Clojure notation clearly distinguishes class attributes/methods from instance methods eg. CSVFormat/DEFAULT vs. (.get record "greeting").

I'll still be reaching for Python when a CSV file comes my way, but I was pleasantly surprised at how neat the Clojure version of this code ended up.

(my software/consulting business)

links

social

Supporting

FSF member since 2006 Become a Conservancy Supporter!