eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
396 stars 62 forks source link

rename 'Iterable' to 'Stream' w/o breaking code #7233

Open gavinking opened 7 years ago

gavinking commented 7 years ago

Alright, so here's a thing I've wanted to do forever. It turns out that Iterable is a really terrible name for the things we call "streams". The whole perspective of working with streams is to not think of procedurally "iterating" over them. Now, sure, Ceylon's Iterable historically grew out of something that filled the same role as Java's interface of the same name, but it has grown into something quite different.

Now, we could renameIterable to Stream without breaking any existing source code simply by creating an alias for it:

shared interface Iterable<out Element=Anything,
                          out Absent=Null> 
        given Absent satisfies Null
        => Stream<Element,Absent>;

We could even make it binary compatible by simply munging the name Stream back to Iterable in the backend, but at this point I'm not sure that's even worth the effort, since we're breaking binary compatibility in the next release anyway.

Thoughts?

gavinking commented 7 years ago

hey, y'know what would be the coolest thing ever?

A renamed annotation that:

renamed ("Iterable")
shared interface Stream<out Element=Anything,
                          out Absent=Null> 
        given Absent satisfies Null { ... }

That would be cool and not even very hard to implement!

jvasileff commented 7 years ago

but it has grown into something quite different

I'm curious to know what Stream is meant to indicate, and how it compares to Iterable.

At a very practical level, 1.3.3's Iterables:

How does the word "stream" fit in with these properties? Are there other properties that should help guide the naming? Is it all the default methods?

It's probably worth noting that the value {1.0, 4.0^0.5, 9.0^0.5} is a very specific type of thing, whereas {Float+} is a very general concept, despite the former's type being no more specific than the latter.

jvasileff commented 7 years ago

That would be cool and not even very hard to implement!

Yeah it would be. It would be a lot nicer than, e.g., ceylon.json's JsonObject alias.

ghost commented 7 years ago

hey, y'know what would be the coolest thing ever? A renamed annotation [...]

I don't really like that idea; it feels weird to have an annotation imply a whole declaration like that. I'd much rather have the IDEs prefer to use the aliased declaration of a deprecated alias:

deprecated("Use [[Stream]] instead.")
see(class Stream)
shared interface Iterable<out Element = Anything, out Absent = Null> 
    given Absent
        satisfies Null
=> Stream<Element, Absent>;

This would avoid introducing a new annotation whose behavior feels completely alien to the behaviors of the current annotations.

I don't really have an opinion about changing "Iterable" to "Stream", though. I wouldn't mind either way.

jvasileff commented 7 years ago

I'd much rather have the IDEs prefer to use the aliased declaration of a deprecated alias

That would not accomplish the goals of a) maintaining binary compatibility and b) having the new source code be minimally complicated and cluttered by workarounds for past naming mistakes.

someth2say commented 7 years ago

"hey, y'know what would be the coolest thing ever? A renamed annotation [...]"

Don't we already have an alias annotation doing something barely similar, but only at IDE level? Can't we just add the missing typechecker and backend behaviours?

gavinking commented 7 years ago

@someth2say well I agree that aliased is kinda similar, but it's really just a hint, meant for people migrating from other languages. It's doesn't have the effect of changing the binary code generated.

someth2say commented 7 years ago

@gavinking well, the point of having renamed is helping migration from <1.4 to >=1.4, isn't it? Binary changing effects are to be added.

BTW, getting back to the beginning of the issue,I actually ended liking Iterable, but I agree Stream is more "mainstream", and would help Ceylon adoption. So +1 for the change.

ghost commented 7 years ago

@jvasileff

I have oppositions, but I think someone pursuing this idea should open a thread about it, instead of discussing it here.

luolong commented 7 years ago

To be honest, I like the @Zambonifofex's proposal even better. Less magic there.

In the end, the renamed annotation would perform exactly the same function, but do it in a slightly unsafer way -- the name of the interface in the annotation argument has a slight chance of being mistyped or "forgotten" in the course of some refactoring...

FroMage commented 7 years ago

I don't think it's worth changing, because our definition of Stream is different to other languages anyway, and especially now that reactive streams are becoming mainstrearm, it's extremely confusing to speak to people about Java streams and reactive streams, which have absolutely nothing in common.

gavinking commented 7 years ago

So what's the consensus here? To not make the change?

CPColin commented 7 years ago

I like the idea of making the change, if only to make parts of the documentation like this page more clear.

gdejohn commented 7 years ago

I always disliked the name Iterable. Stream would be great.

jean-morissette commented 6 years ago

IMHO, Stream carries better the meaning of internal/functional/lazy iterator, while Iterable suggests external/procedural/eager iterator.

xkr47 commented 6 years ago

Source

gavinking commented 5 years ago

I'm changing my mind on this. As pointed out by others, there is a difference between a "stream", and an "iterable", in that a stream is in some sense stateful and cannot be re-iterated, whereas an iterable is in this sense state less and can be re-iterated "from the start", whatever that means, given the semantics of the particular iterable.

And a second problem is: if we rename Iterable to Stream, what do we call Iterator.

So I'm now thinking of leaving the name alone.

xkr47 commented 5 years ago

Without trying to force this issue in either direction, an alternative could be: IterableSource or Streamable IteratorStream

CPColin commented 5 years ago

Based on the above two comments, I suppose that between Iterable and Iterator, it makes more sense for Iterator to be renamed to Stream than for Iterable to be renamed.

xkr47 commented 5 years ago

I do hope we have some suitable terms remaining when the async revolution hits Ceylon :)

lucono commented 5 years ago

Based on the above two comments, I suppose that between Iterable and Iterator, it makes more sense for Iterator to be renamed to Stream than for Iterable to be renamed.

I really like this idea. Seems to me a viable relationship (and bridge) between Iterables and Streams in the language.

dlkw commented 5 years ago

Would that mean that the "streaming methods" that are usually associated with the notion of streams (like map, filter, reduce etc) should be moved from Iterable to Iterator aka Stream?

Further, I think this is connected to #6406.

jvasileff commented 5 years ago

@dlkw that would create bad ergonomics, like in java where you always have to call stream() before being able to do anything useful.

Also, methods like map() should preserve the source's re-iterability, which wouldn't be possible on Iterators.

jvasileff commented 5 years ago

Based on the above two comments, I suppose that between Iterable and Iterator, it makes more sense for Iterator to be renamed to Stream than for Iterable to be renamed.

Stream is a really nice name, and I think it would be a waste to use it on something so useless as an Iterator, which is primarily just a temporary object used by for loops. And even if Stream isn't otherwise used, I'm not sure giving Iterators a nice name would do anything other than encourage overuse (misuse).

CPColin commented 5 years ago

Sounds like consensus is forming around leaving the names as they are?

gavinking commented 5 years ago

Sounds like consensus is forming around leaving the names as they are?

Yes.

xkr47 commented 5 years ago

@dlkw wrote:

Would that mean that the "streaming methods" that are usually associated with the notion of streams (like map, filter, reduce etc) should be moved from Iterable to Iterator aka Stream?

@jvasileff wrote:

Also, methods like map() should preserve the source's re-iterability, which wouldn't be possible on Iterators.

Nothing stopping from having both interfaces include them :) Maybe even a common superinterface...

OFFTOPIC Sorry, can't resist.. A totally different way of looking at Iterable vs Iterator would be to see Iterable simply being a source of Iterators.. an Iterator producing Iterators..

interface Iterable<out Element=Anything, out Absent=Null>
    satisfies Iterator<Iterator<Element>> {
   next() => iterator();
}

And while at it, why not introduce a OmniPresent type parameter for indicating infinite Iterables..

"""Use:
* Iterable<Element[, Null, Null]> for possibly-empty streams
* Iterable<Element, Nothing[, Null]> for nonempty streams
* Iterable<Element, Nothing, Nothing> for infinite streams
"""
interface Iterable<out Element=Anything, out Absent=Null, out OmniPresent=Null> 
    given Absent satisfies Omnipresent
{
    shared default Iterable<Element,Absent&OmniPresent,OmniPresent> rest => skip(1);
}
CPColin commented 5 years ago

I wouldn't mind if Iterator<T> also satisfied Iterable<T> (and {T*}) and could be used in for…in loops. That's always bugged me in Java, too.

jvasileff commented 5 years ago

I wouldn't mind if Iterator also satisfied Iterable (and {T*}) and could be used in for…in loops.

I think having Iterator extend Iterable (or, perhaps vice versa) would create confusion around the re-iterability of Iterables, implying that all Iterables are re-iterable (which they are not), and that Iterators should always be used for non-reiterable streams.

A common argument against having the type system indicate re-iterability is that it would be prone to errors. You would have two types with basically identical APIs, and people may too easily (and incorrectly) use Iterables for single-shot streams.

That's always bugged me in Java, too

Yeah. In both Java and Ceylon, I've run into cases where I've wanted to use multiple for loops to processes a single Iterator, passing control among them.

xkr47 commented 5 years ago

Why could we not just let the for (a in b) accept both Iterable and Iterator for b? This would imo be clearer than having Iterators satisfy Iterable..

@jvasileff wrote:

Yeah. In both Java and Ceylon, I've run into cases where I've wanted to use multiple for loops to processes a single Iterator, passing control among them.

As the Iterator.next() contract requires implementations to indefinitely return finished after having done so once, my above proposal would also allow breaking out of one loop and resuming iteration in another..