Skip to content

Commit b86d764

Browse files
committed
Drop on LinearSeq needs to return collection of same type as it was called on.
This is achieved by putting it into a new trait, LinearSeqLike.
1 parent 475e433 commit b86d764

File tree

2 files changed

+76
-16
lines changed

2 files changed

+76
-16
lines changed

src/strawman/collections/CollectionStrawMan6.scala

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,28 +124,36 @@ object CollectionStrawMan6 extends LowPriority {
124124
def length: Int
125125
}
126126

127-
/** Base trait for linearly accessed sequences */
128-
trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self =>
127+
/** Base trait for linearly accessed sequences that have efficient `head` and
128+
* `tail` operations.
129+
* Known subclasses: List, LazyList
130+
*/
131+
trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self =>
129132

130133
/** To be overridden in implementations: */
131134
def isEmpty: Boolean
132135
def head: A
133136
def tail: LinearSeq[A]
134137

138+
/** `iterator` is overridden in terms of `head` and `tail` */
135139
def iterator = new Iterator[A] {
136140
private[this] var current: Seq[A] = self
137141
def hasNext = !current.isEmpty
138142
def next = { val r = current.head; current = current.tail; r }
139143
}
140144

145+
/** `length is defined in terms of `iterator` */
141146
def length: Int = iterator.length
142147

143-
@tailrec final def apply(n: Int): A =
144-
if (n == 0) head else tail.apply(n - 1)
145-
146-
/** Optimized version of `drop` that avoids copying */
147-
@tailrec final override def drop(n: Int) =
148-
if (n <= 0) this else tail.drop(n - 1)
148+
/** `apply` is defined in terms of `drop`, which is in turn defined in
149+
* terms of `tail`.
150+
*/
151+
override def apply(n: Int): A = {
152+
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
153+
val skipped = drop(n)
154+
if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString)
155+
skipped.head
156+
}
149157
}
150158

151159
/** Base trait for strict collections that can be built using a builder.
@@ -221,6 +229,28 @@ object CollectionStrawMan6 extends LowPriority {
221229
extends IterableLike[A, C]
222230
with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote
223231

232+
/** Base trait for linear Seq operations */
233+
trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] {
234+
235+
/** Optimized version of `drop` that avoids copying
236+
* Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`,
237+
* because the `...MonoTransforms` traits make no assumption about the type of `Repr`
238+
* whereas we need to assume here that `Repr` is the same as the underlying
239+
* collection type.
240+
*/
241+
override def drop(n: Int): C[A @uncheckedVariance] = { // sound bcs of VarianceNote
242+
def loop(n: Int, s: Iterable[A]): C[A] =
243+
if (n <= 0) s.asInstanceOf[C[A]]
244+
// implicit contract to guarantee success of asInstanceOf:
245+
// (1) coll is of type C[A]
246+
// (2) The tail of a LinearSeq is of the same type as the type of the sequence itself
247+
// it's surprisingly tricky/ugly to turn this into actual types, so we
248+
// leave this contract implicit.
249+
else loop(n - 1, s.tail)
250+
loop(n, coll)
251+
}
252+
}
253+
224254
/** Operations over iterables. No operation defined here is generic in the
225255
* type of the underlying collection.
226256
*/

tests/run/colltest6/CollectionStrawMan6_1.scala

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,28 +125,36 @@ object CollectionStrawMan6 extends LowPriority {
125125
def length: Int
126126
}
127127

128-
/** Base trait for linearly accessed sequences */
129-
trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self =>
128+
/** Base trait for linearly accessed sequences that have efficient `head` and
129+
* `tail` operations.
130+
* Known subclasses: List, LazyList
131+
*/
132+
trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self =>
130133

131134
/** To be overridden in implementations: */
132135
def isEmpty: Boolean
133136
def head: A
134137
def tail: LinearSeq[A]
135138

139+
/** `iterator` is overridden in terms of `head` and `tail` */
136140
def iterator = new Iterator[A] {
137141
private[this] var current: Seq[A] = self
138142
def hasNext = !current.isEmpty
139143
def next = { val r = current.head; current = current.tail; r }
140144
}
141145

146+
/** `length is defined in terms of `iterator` */
142147
def length: Int = iterator.length
143148

144-
@tailrec final def apply(n: Int): A =
145-
if (n == 0) head else tail.apply(n - 1)
146-
147-
/** Optimized version of `drop` that avoids copying */
148-
@tailrec final override def drop(n: Int) =
149-
if (n <= 0) this else tail.drop(n - 1)
149+
/** `apply` is defined in terms of `drop`, which is in turn defined in
150+
* terms of `tail`.
151+
*/
152+
override def apply(n: Int): A = {
153+
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
154+
val skipped = drop(n)
155+
if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString)
156+
skipped.head
157+
}
150158
}
151159

152160
/** Base trait for strict collections that can be built using a builder.
@@ -222,6 +230,28 @@ object CollectionStrawMan6 extends LowPriority {
222230
extends IterableLike[A, C]
223231
with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote
224232

233+
/** Base trait for linear Seq operations */
234+
trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] {
235+
236+
/** Optimized version of `drop` that avoids copying
237+
* Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`,
238+
* because the `...MonoTransforms` traits make no assumption about the type of `Repr`
239+
* whereas we need to assume here that `Repr` is the same as the underlying
240+
* collection type.
241+
*/
242+
override def drop(n: Int): C[A @uncheckedVariance] = { // sound bcs of VarianceNote
243+
def loop(n: Int, s: Iterable[A]): C[A] =
244+
if (n <= 0) s.asInstanceOf[C[A]]
245+
// implicit contract to guarantee success of asInstanceOf:
246+
// (1) coll is of type C[A]
247+
// (2) The tail of a LinearSeq is of the same type as the type of the sequence itself
248+
// it's surprisingly tricky/ugly to turn this into actual types, so we
249+
// leave this contract implicit.
250+
else loop(n - 1, s.tail)
251+
loop(n, coll)
252+
}
253+
}
254+
225255
/** Operations over iterables. No operation defined here is generic in the
226256
* type of the underlying collection.
227257
*/

0 commit comments

Comments
 (0)