ceylon
ceylon copied to clipboard
rename 'Iterable' to 'Stream' w/o breaking code
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?
hey, y'know what would be the coolest thing ever?
A renamed annotation that:
- tells the typechecker and model loaders to automatically generate an alias with the old name of a decaration,
- tells the backends to keep using the old name in compiled code, and
- tells the IDEs to prompt to rename to the new name.
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!
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:
- Can be iterated with good performance
- May or may not:
- be empty
- be known to be non-empty, if non-empty
- support efficient random access
- be finite
- be lazy
- be readable more than once
- be stable or mutable
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.
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.
hey, y'know what would be the coolest thing ever? A
renamedannotation [...]
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.
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.
"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?
@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.
@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.
@jvasileff
I have oppositions, but I think someone pursuing this idea should open a thread about it, instead of discussing it here.
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...
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.
So what's the consensus here? To not make the change?
I like the idea of making the change, if only to make parts of the documentation like this page more clear.
I always disliked the name Iterable. Stream would be great.
IMHO, Stream carries better the meaning of internal/functional/lazy iterator, while Iterable suggests external/procedural/eager iterator.
- any thing or place from which something comes, arises, or is obtained; origin: Which foods are sources of calcium?
- the beginning or place of origin of a stream or river.
- a manufacturer or supplier.
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.
Without trying to force this issue in either direction, an alternative could be:
Iterable → Source or Streamable
Iterator → Stream
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 do hope we have some suitable terms remaining when the async revolution hits Ceylon :)
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.
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.
@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.
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).
Sounds like consensus is forming around leaving the names as they are?
Sounds like consensus is forming around leaving the names as they are?
Yes.
@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);
}
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.
I wouldn't mind if Iterator<T> also satisfied Iterable<T> (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.
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..