-
Notifications
You must be signed in to change notification settings - Fork 72
Conversation
b52b6bb
to
236fa97
Compare
|
||
import scala.{Int, IndexOutOfBoundsException} | ||
|
||
trait SeqView[+A] extends SeqOps[A, View, View[A]] with View[A] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could it extends SeqOps[A, SeqView, SeqView[A]]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that if we do that transformation operations have to always return a SeqView
, and sometimes (e.g. filter
) this is not possible without computing the entire result of the transformation operation, which would defeat the purpose of using views.
I'll have to think about it more, it's a big topic. We'd have to do more to support the example here http://docs.scala-lang.org/overviews/collections/views.html
Could |
If |
Another data point: in 2.12, |
@lrytz good point. Currently neither |
There are conflicting goals between lazyness and providing "view" functionality. I think in the past, views were often mentioned as a way to fuse operations. But 2.12 views have bugs, and you need to know which operations are lazy, which are forcing; that even depends on the collection/view type, your example of
As far as I know, iterators are nowadays recommended for fusing (?), as they provide a restricted interface that makes accidental forcing less likely. I think this is by far the most common use case: The less common use case is actually providing a transformed view onto some data. Maybe for this use case, providing a rich interface is more important than trying to ensure lazyness? Not sure. I wonder how much code we break if we go for a simpler, but safer interface. I just realized another problem in 2.12:
In reality, with mutable data, people would not use views but something like scala.rx. In any case, there is a need for clear user recommendataions. |
Historic thread with lots of good points: https://groups.google.com/forum/#!topic/scala-debate/M8s8FmASL8Y. @julienrf can you summarize / reference discussions about views that took place during the strawman design period? |
Actually, there hasn’t been much discussion about views. There was that issue, #13, which digressed in several directions, but whose main takeaway point is that mutable views are probably too confusing to be useful. We also had meeting discussions about views and equality. We couldn’t find a sensible definition of equality for views. Especially because equality between collections is not defined at the level of iterable but only in |
To make progress on this I propose that we don’t enrich views with transformation operations that will force the evaluation of their elements (so, This will be a source of backward incompatibility. However, my guess is that so far views weren’t heavily relied on, so hopefully we won’t break much things. Then, we will see when we will build the community build if this guess is confirmed or not, and we will consider or not fixing these incompatibilities. What do you think? |
agree |
If views are their own hierarchy and not subtypes of collection types, I wonder what they add to iterators. I understand that views are multiple-use/traversal. But what are acutal use cases that they enable (in the current design / with this PR)? If I write some code that acts on a collection type, I cannot pass in a view; that seems to be the main selling point in http://docs.scala-lang.org/overviews/collections/views.html. So I have to write code specifically for views, which then doesn't work for ordinary collection types. Maybe that tradeoff is acceptable? At least one can still use the view-based style which simplifies implementing certian algorithms. |
Discussion summary
So our conclusion was that we continue with this PR and keep views in their separate hierarchy. |
|
||
/** A collection containing the last `n` elements of this collection. */ | ||
override def takeRight(n: Int): C = fromSpecificIterable(view.takeRight(n)) | ||
override def reverse: C = fromSpecificIterable(new IndexedView.Reverse(this)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between view.takeRight
and IndexedView.TakeRight
(line 39) is that the former builds a view of a view, whereas the latter builds just one view. The idea is to remove one level of indirection. If that would have been optimized anyway by the JIT then we should switch to view.takeRight
instead.
override def slice(from: Int, until: Int): Vector[A] = | ||
take(until).drop(from) | ||
|
||
override def splitAt(n: Int): (Vector[A], Vector[A]) = (take(n), drop(n)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed because this is exactly the default implementation.
@@ -171,11 +171,6 @@ final class Vector[+A] private[immutable] (private[collection] val startIndex: I | |||
dropRight(1) | |||
} | |||
|
|||
override def slice(from: Int, until: Int): Vector[A] = | |||
take(until).drop(from) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed because IndexedView.Slice
does the same job without creating an intermediate Vector
(like take(until)
does)
Give me some time to review it still, I haven't yet acutally; so far I just handwaved around views in general :-) |
Remove some upper bound constraints on Ops traits. Remove unnecessary ArrayLike trait.
Add IndexedView.Slice Reduce indirection levels by generalizing views to accept XxxOps parameters instead of collection types Add MapView.FilterKeys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good!
import scala.Predef.{<:<, intWrapper} | ||
|
||
/** View defined in terms of indexing a range */ | ||
trait IndexedView[+A] extends IndexedSeqOps[A, View, View[A]] with SeqView[A] { self => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we call it IndexedSeqView
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it would make more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure that you can have a sensible indexing without a seq, so to me IndexedView
(and, indeed, IndexedOps
) make just as much sense to me.
further discussion at scala/bug#10919 |
My goal with this PR was to fix #160. In short, the problem was that, currently, the view of a
Map
or aSeq
has typeView
, and doesn’t support commonMap
andSeq
operations (such asget
orreverse
, respectively). This is an incompatibility compared to the old collections.This PR adds a
SeqView
type and aMapView
type, supportingSeq
operations andMap
operations, respectively. However, their usefulness is quite limited: if you call, say,filter
on aSeqView
, you end up with aView
, which doesn’t support anymoreSeq
operations… (same forMapView
). More generally, most transformation operations give back aView
, excepted a few.This behaviour differs from the old collections, where transformation operations always return the same collection type (even when applied to views). We could replicate that behaviour for the sake of compatibility, but I don’t think that would be a good idea because to do so we would have to (sometimes) force view elements, which would be surprising because as a user I expect transformations applied to views to always be lazy.
That being said, even the limited
SeqView
that I introduced in this PR can be surprising because it supportsreverse
, which returns aView
although its implementation necessarily evaluates all the underlying collection’s elements. Actually, in our currentView
, we also have operations that return views although they fully compute an intermediate collection:takeRight
,dropRight
,grouped
,sliding
,groupBy
,scanRight
,tails
,inits
.I wish we could make it really clear in the types that some transformation operations won’t create intermediate collections and some others will. But that would complicate a lot more the
Ops
traits. Instead, I suggest that we try to limit the operations provided by views so that users have less chance to accidentally create intermediate collections when transforming views. Therefore, I would not try to replicate the old behaviour that makesSeqView#filter
return aSeqView
(for instance).While working on that I also did the following changes:
— add a test checking that calling
.view
on a view is effectively a no-op (that wasn’t the case!),— turn all view implementations into classes instead of case classes (the motivation is that it should reduce the bytecode size, since we don’t use any feature of case classes),
— optimized
knownSize
for smallMap
implementations.The code I pushed doesn’t compile with Dotty. Actually my understanding is that 9f4f6f6 should be rejected by scalac but is not (it is rejected by dotc, though). I tried to fix the issue but my fix makes dotc crash:
[update] I’ve just tried with the latest dotty nightly and it seems that the code compiles. However there is another error due to scala/scala3#3965