Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interop from clojure a with non-standard iterative java API

I am working in clojure with a java class which provides a retrieval API for a domain specific binary file holding a series of records.

The java class is initialized with a file and then provides a .query method which returns an instance of an inner class which has only one method .next, thus not playing nicely with the usual java collections API. Neither the outer nor inner class implements any interface.

The .query method may return null instead of the inner class. The .next method returns a record string or null if no further records are found, it may return null immediately upon the first call.

How do I make this java API work well from within clojure without writing further java classes?

The best I could come up with is:


(defn get-records
  [file query-params]
  (let [tr (JavaCustomFileReader. file)]
    (if-let [inner-iter (.query tr query-params)] ; .query may return null                                                               
      (loop [it inner-iter
             results []]
       (if-let [record (.next it)]
         (recur it (conj results record))
         results))
      [])))

This gives me a vector of results to work with the clojure seq abstractions. Are there other ways to expose a seq from the java API, either with lazy-seq or using protocols?

like image 344
Alex Stoddard Avatar asked Jul 10 '12 13:07

Alex Stoddard


People also ask

Why do you use Clojure?

One of the big reasons I'm able to use Clojure so freely is the seamless interop with Java. Execute Clojure from Java. Calling Clojure from Java is as simple as loading the .clj file and invoking a method from that file.

How do I use Java arrays in Clojure?

Arrays. Clojure supports the creation, reading and modification of Java arrays. It is recommended that you limit use of arrays to interop with Java libraries that require them as arguments or use them as return values. Note that many other Clojure functions work with arrays such as via the seq library.

What are the primitive types supported by CL Clojure?

Clojure has support for high-performance manipulation of, and arithmetic involving, Java primitive types in local contexts. All Java primitive types are supported: int, float, long, double, boolean, char, short, and byte.

How to read data from Clojure’s public APIs?

A convenience method read for reading data using Clojure’s edn reader IFns provide complete access to Clojure’s APIs. You can also access any other library written in Clojure, after adding either its source or compiled form to the classpath. The public Java API for Clojure consists of the following classes and interfaces:


3 Answers

Without dropping to lazy-seq:

(defn record-seq
  [q]
  (take-while (complement nil?) (repeatedly #(.next q))))

Instead of (complement nil?) you could also just use identity if .next does not return boolean false.

(defn record-seq
  [q]
  (take-while identity (repeatedly #(.next q))))

I would also restructure a little bit the entry points.

(defn query
  [rdr params]
  (when-let [q (.query rdr params)]
    (record-seq q)))

(defn query-file
  [file params]
  (with-open [rdr (JavaCustomFileReader. file)]
    (doall (query rdr params))))
like image 148
kotarak Avatar answered Nov 15 '22 08:11

kotarak


Seems like a good fit for lazy-seq:

(defn query [file query]
  (.query (JavaCustomFileReader. file) query))

(defn record-seq [query]
  (when query
    (when-let [v (.next query)]
      (cons v (lazy-seq (record-seq query))))))

;; usage:
(record-seq (query "filename" "query params"))
like image 20
Gert Avatar answered Nov 15 '22 10:11

Gert


Your code is not lazy as it would be if you were using Iterable but you can fill the gap with lazy-seq as follows.

(defn query-seq [q]
  (lazy-seq
    (when-let [val (.next q)]
      (cons val (query-seq q)))))

Maybe you shoul wrap the query method to protect yourself from the first null value as well.

like image 41
sortega Avatar answered Nov 15 '22 10:11

sortega