Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation of "self-documenting" methods on TraversableOnce is needed #9624

Closed
scabug opened this issue Jan 14, 2016 · 4 comments
Closed

Comments

@scabug
Copy link

scabug commented Jan 14, 2016

I ran into a problem described in this gist whereby a manually-implemented TraversableOnce saw incorrect behaviour of the reduceLeft method because the reduceLeft implementation assumes that calls to TraversableOnce.isEmpty are idempotent (i.e. they do not consume the traversable).

This is, of course, how one would expect the method to be implemented on Iterator but I believe that the current documentation of the methods falls well short:

  /** Self-documenting abstract methods. */
  def foreach[U](f: A => U): Unit
  def isEmpty: Boolean
  def hasDefiniteSize: Boolean

I believe that the documentation should explicitly say that implementations of isEmpty should be idempotent.

Furthermore, I believe that hasDefiniteSize is absolutely not self-documenting (without understanding where it is called). For example, what does one expect the following to print?

scala> Stream.continually(1).iterator.hasDefiniteSize //prints 'false' of course

scala> List(1).iterator.hasDefiniteSize //what does this print?

A quick survey of colleagues resulted in everyone assuming that the latter should return true (it doesn't).

Note also that the behaviour of Traversable and Iterator in this case:

scala> val it = List(1).iterator; it foreach (_ => println(it.isEmpty)) //prints 'true'

scala> val it = Traversable(1); it foreach (_ => println(it.isEmpty)) //prints 'false'
@scabug
Copy link
Author

scabug commented Jan 14, 2016

Imported From: https://issues.scala-lang.org/browse/SI-9624?orig=1
Reporter: @oxbowlakes
Affected Versions: 2.11.7

@scabug
Copy link
Author

scabug commented Feb 2, 2016

@szeiger said:
PR: scala/scala#4939

@scabug
Copy link
Author

scabug commented Feb 3, 2016

@Blaisorblade said:
Regarding the Traversable/Iterator difference (in the last point of the report): It is not addressed in the PR. But the case shown appears pretty understandable — I guess when the body runs, the iterator was consumed and is thus empty, while the Traversable doesn't get consumed and stays full. Annoying, but if you don't like that semantics in this case, don't use iterators I guess.
Is there a more complicated scenario where the behavior is harder to explain? Is the documentation on this behavior incomplete?

@scabug
Copy link
Author

scabug commented Feb 3, 2016

@szeiger said:
Right, I think that part simply works as expected. The difference is not due to isEmpty consuming an element. When you get to the first println, the only element of the Iterator has already been consumed (it's passed to your lambda after all), so the Iterator is empty at that point.

Similarly, the first part of this ticket (in the gist) also does the "right" thing but maybe that's because I didn't understand the reason for using a Traversable instead of a TraversableOnce there in the first place. If you implement a Traversable even though your source can only be traversed once and you do not cache the data, it will go wrong at some point. I would implement an Iterator in these cases but that's already the first recommendation in the scaladoc for TraversableOnce:

 *  Directly subclassing `TraversableOnce` is not recommended - instead,
 *  consider declaring an `Iterator` with a `next` and `hasNext` method,
 *  creating an `Iterator` with one of the methods on the `Iterator` object,
 *  or declaring a subclass of `Traversable`.

On second thought, maybe this comment should be changed to warn that the alternative of subclassing Traversable only applies if you really have a source that can be traversed repeatedly (even if all you need is the TraversableOnce part of it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants