dilvan
dilvan

Reputation: 2239

How to implement clojure ISeq inJava?

I want to pass to Clojure a map of Java functions. The functions use Java anonymous classes that implement the IFn interface. I also need to implement the ISeq interface to pass lists. The ISeq interface is simple but has no documentation. I need to know what each method in ISeq does and/or find Java code of a class that implements all ISeq methods (I do not want to copy my lists to Clojure structures).

Upvotes: 1

Views: 471

Answers (2)

Michał Marczyk
Michał Marczyk

Reputation: 84351

This fragment of the question text appears to suggest that calling seq on an array, which is what the linked answer to a question about converting Java arrays to Clojure seqs suggests, copies the contents of the array to a fresh seq:

(I do not want to copy my lists to Clojure structures)

This is not the case. What happens instead is that seq returns a wrapper seq backed by the array, as evidenced by the following REPL interaction:

Clojure 1.9.0
user=> (def xs (object-array [:foo :bar]))
#'user/xs
user=> (seq xs)
(:foo :bar)
user=> (def s1 (doall (seq xs)))
#'user/s1
user=> s1
(:foo :bar)
user=> (aset xs 0 :quux)
:quux
user=> (seq xs)
(:quux :bar)
user=> s1
(:quux :bar)

Notice that mutating the underlying array results in an observable change to the seq on the array that was created before the mutation.

This contrasts with the behaviour of seq when passed a java.util.List that is not simultaneously an ISeq, in which case a fresh seq will indeed be allocated:

Clojure 1.9.0
user=> (def ys (java.util.ArrayList. [:foo :bar]))
#'user/ys
user=> ys
[:foo :bar]
user=> (def s2 (seq ys))
#'user/s2
user=> s2
(:foo :bar)
user=> (.set ys 0 :quux)
:foo
user=> ys
[:quux :bar]
user=> s2
(:foo :bar)

This need not be a terrible outcome, as the seq will be chunked, but it may make sense to avoid any wrapping.

Even then, however, the exact usage pattern should be examined, as it is entirely possible that the Java lists could be passed to Clojure directly and processed without any intermediate seq allocations. For example, Iterables can be reduce'd directly and efficiently with no such allocations.

So, I would reexamine the assumption that the lists that need to be transferred from the Java side to Clojure need cross the divide as ISeq instances. This is not to say that under no circumstances can such an arrangement make sense – merely that from the limited detail provided in the question text it doesn't follow that this is necessary in this particular case.

As for how to implement ISeq if you decide to do so in the end, I concur with Taylor Wood's suggestion to examine clojure.lang.ASeq.

Upvotes: 1

Taylor Wood
Taylor Wood

Reputation: 16194

I need to know what each method in ISeq does

You should also understand the interfaces it extends:

public interface Seqable {
    ISeq seq(); // returns an ISeq instance
}

public interface IPersistentCollection extends Seqable {
    // returns the count of this coll
    int count();
    // returns a new coll with o cons'd onto it
    IPersistentCollection cons(Object o);
    // returns a new empty coll (of same type?)
    IPersistentCollection empty();
    // compares this coll against o for equality
    boolean equiv(Object o);
}

public interface ISeq extends IPersistentCollection {
    // returns the first item in this seq
    Object first();
    // returns a new seq of every item in this except the first
    ISeq next();
    // returns a new seq of every item in this except the first
    // ASeq returns empty for empty
    ISeq more();
    // returns a new seq with o cons'd onto this seq
    ISeq cons(Object o);
}

...or find Java code of a class that implements all ISeq methods

The ASeq abstract class implements (most of) ISeq. Classes that extend ASeq implement ISeq.next().

There's surely an easier way to accomplish your goal besides reimplementing ISeq. Maybe looking at Clojure's Java implementation will give you some ideas.

Upvotes: 1

Related Questions