eclipse-archived / ceylon

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

Items of iterable comprehesion are evaluated each times when iterated, which potentially leads to mutations #5913

Closed LisiLisenok closed 8 years ago

LisiLisenok commented 8 years ago

Consider counter - each calling increases a total count by 1. And comprehesion of results of the counting (at least as expected). But this comprehesion recalculates its items each time when evaluated. Hope the code below is more clear... Problems:

At the same time sequence [] shows different results (works as expected)

class CounterCall() { shared variable Integer count = 0;

shared Integer call() {
    return count ++;
}

}

shared void testIterable() { CounterCall counterIter = CounterCall(); value iter = [ for ( i in 0 : 2 ) counterIter.call() ]; print ( "total calls counterIter.count array: iter" ); print ( "total calls counterIter.count array: iter" ); print ( "total calls counterIter.count array: iter" );

CounterCall counterSeq = CounterCall();
value seq = [ for ( i in 0 : 2 ) counterSeq.call() ];
print ( "total calls ``counterSeq.count`` array: ``seq``" );
print ( "total calls ``counterSeq.count`` array: ``seq``" );
print ( "total calls ``counterSeq.count`` array: ``seq``" );

}

output: total calls 0 array: { 0, 1 } total calls 2 array: { 2, 3 } total calls 4 array: { 4, 5 } total calls 2 array: [0, 1] total calls 2 array: [0, 1] total calls 2 array: [0, 1]

jvasileff commented 8 years ago

@LisiLisenok that is the nature of iterables created with { ... }. They are lazy and their elements will be re-evaluated each time they are iterated. As your code demonstrates, using [ ... ] is the right approach if you do not want the lazy behavior.

(I assume you meant value iter = { for ( i in 0 : 2 ) counterIter.call() };)

LisiLisenok commented 8 years ago

@jvasileff I agree. But even with lazy evaluation it may be performed just once. This helps with mutability at least. In any case it would be good to mention this behavior and this difference between {...} and [...] in the documentation.

Thank you for clarification. Lets consider this is not a bug)

tombentley commented 8 years ago

Which documentation were you looking at which didn't mention this? On 14 Jan 2016 7:34 pm, "Lisi" notifications@github.com wrote:

@jvasileff https://github.com/jvasileff I agree. But even with lazy evaluation it may be performed just once. This helps with mutability at least. In any case it would be good to mention this behavior and this difference between {...} and [...] in the documentation.

Thank you for clarification. Lets consider this is not a bug)

— Reply to this email directly or view it on GitHub https://github.com/ceylon/ceylon/issues/5913#issuecomment-171753850.

fwgreen commented 8 years ago

The Streams, sequences and tuples section of the language tour don't mention it. It's only mentioned, in passing, in the Comprehensions section. Many of the features announced in blog posts should probably be highlighted in the tour, as first time users will go there first. At any rate, only people who write frameworks read language specs :smile:

LisiLisenok commented 8 years ago

@fwgreen thanks. I am one who has never read language specs. Hope some day... @tombentley Tom, looking in language tour and language module API I thought that {...} is like a sugar for Iterable or language internal type which exactly corresponds to Iterable interface specification. But that's not completely true. After my own experiments and John clarification I understand that comprehesions are some separated types with their own specifications. And these types are not described in API documentation. I would propose to add here and here a short paragraph describing comprehesions evaluation and other specific implementations of Iterable interface

tombentley commented 8 years ago

Agreed it should be clarified in the tour. Something like "comprehensions are lazy and not memoized (so iterating a comprehension will cause each element to be reevaluated every time its needed)", or something like that.

Not quite so sure about the Iterable doc itself, since really that's about the iterable contract itself, and comprehensions are just one implementation of that. But I guess it could fit after the third paragraph if we can find the right words.

A Pull request would be a great help!

FroMage commented 8 years ago

comprehensions are lazy and not memoized

That's not special for comprehensions, because that behaviour is also in effect for any Iterable literal, no? { f() } will also always call f() on every iteration, no?

tombentley commented 8 years ago

Yeah, so I suppose we also need something on http://ceylon-lang.org/documentation/1.2/tour/sequences/. But I think saying it again (reiterating it, haha) on the comprehensions page is not a bad thing.

LisiLisenok commented 8 years ago

@FroMage [...] satisfies Iterable but evaluates just once - when initialized, Array, ArrayList and so on - all satisfied Iterable but evaluates just once. As I understand only {...} calls f() on every iteration, or not?

FroMage commented 8 years ago

Yes, all {} Iterable literals are lazy and non-memoised (always (re)evaluate on iteration), and all [] Sequential literals are eager and memoised.

The fact that Sequential satisfies Iterable does not imply that it makes iterating them lazy.

gavinking commented 8 years ago

I have added documentation to the tour. Closing.