ceylon / ceylon-compiler

DEPRECATED
GNU General Public License v2.0
138 stars 36 forks source link

full interop b/w Iterable and java.lang.Iterable #1754

Open gavinking opened 10 years ago

gavinking commented 10 years ago

To my mind, the biggest remaining point of discomfort with Java interop is the fact that a Ceylon for can't iterate a Java collection, and a Java for can't iterate a Ceylon Iterable. So I've put some thought into this today and come up with what looks like a reasonable solution. First, we add the following interface to ceylon.language:

shared sealed interface Loopable<T> {
    shared Iterator<T> iterator();
}

Now, our existing Iterable<T,N> would inherit Loopable<T>.

The backend should:

Given:

class XIter() satisfies Iterator<X> {
    iterator() => whatever();
}

The backend would produce the following code:

class XIter implements java.util.Iterator<X>, ceylon.language.Iterator<X> {
    private static final Object nil$$$ = new Object();
    private X next$$$ = nil$$$;
    public java.lang.Object next$() {
        return whatever();
    }
    public boolean hasNext() {
        if (next$$$==nil$$$) {
            next$$$ = next$();
        }
        return next$$$!=finished_.getFinished();
    }
    public X next() {
        if (next$$$==nil$$$) {
            return next$();
        }
        else if (next$$$==finished_.getFinished()) {
            next$$$ = null;
            throw new NoSuchElementException();
        }
        else {
            X result = (X) next$$$;
            next$$$ = null;
            return result;
        }
    }
}

Finally, we would replace all calls to Iterator.next() with calls to the following clever utility function:

public <X> java.lang.Object next(Iterator<? extends X> it) {
    if (it instanceof ceylon.language.Iterator) {
        return ((ceylon.language.Iterator<? extends X>) it).next$();
    }
    else {
        return it.hasNext() ? it.next() : finished_.getFinished();
    }
}

WDYT? I don't see any real holes in this. The worse thing about it is the addition of the Loopable interface. But that might even eventually turn out to be useful in some other contexts. And a trivial sealed SMI is not really hurting anything.

gavinking commented 10 years ago

P.S. we might be able to take the same approach with Comparable.

tombentley commented 10 years ago
tombentley commented 10 years ago

Java's Iterable<T> is invariant in T (specifically it declares Iterator<T> iterator() rather than Iterator<? extends T> iterator()), but Loopable<T> needs to be out T if it's to serve as a supertype of Ceylon's Iterable.

tombentley commented 10 years ago

It would still be legal to write:

Loopable l = nothing;
value done = l.iterator().next() is Finished;

Since the Ceylon Iterator gets erased to j.u.Iterator we ought to translate that as

l.iterator().$next()

(because we mean Ceylon's next()), but j.u.Iterator doesn't have such a method.

gavinking commented 10 years ago

Java's Iterable<T> is invariant in T (specifically it declares Iterator<T> iterator() rather than Iterator<? extends T> iterator()), but Loopable<T> needs to be out T if it's to serve as a supertype of Ceylon's Iterable.

Oh, oops, that's annoying. It might be OK, since Loopable is going to be erased anyway.

gavinking commented 10 years ago

It would still be legal to write:

@tombentley note that I say above:

Finally, we would replace all calls to Iterator.next() with calls to the following clever utility function:

gavinking commented 10 years ago

So in light of that, the following solution might be better than the one above. Given:

class MyIterable() satisfies Iterable<String> {
    iterator() => MyIterator();
}

We generate:

class MyIterable() implements ceylon.language.Iterable<String>, java.lang.Iterable<String> {
     public final ceylon.language.Iterable<? extends String> iterator$() { return MyIterator(); }
     public final java.lang.Iterable<String> iterator() { return JavaIterator(iterator$()); }
}

And we transform all calls to iterator() to iterator$().

gavinking commented 10 years ago

This is not for 1.1.5, since it would require a break to binary compatibility. Rescheduling to 1.2.