diff --git a/src/main/scala/strawman/collection/Iterable.scala b/src/main/scala/strawman/collection/Iterable.scala index 079adcda33..dd2814d5dc 100644 --- a/src/main/scala/strawman/collection/Iterable.scala +++ b/src/main/scala/strawman/collection/Iterable.scala @@ -2,7 +2,7 @@ package strawman.collection import scala.annotation.unchecked.uncheckedVariance import scala.reflect.ClassTag -import scala.{Int, Boolean, Array, Any, Unit, StringContext} +import scala.{Int, Boolean, Array, Any, Unit, StringContext, Option} import java.lang.String import strawman.collection.mutable.{ArrayBuffer, StringBuilder} @@ -69,8 +69,7 @@ trait IterableOps[+A] extends Any { /** Is the collection empty? */ def isEmpty: Boolean = !iterator().hasNext - /** The first element of the collection. */ - def head: A = iterator().next() + def headOption: Option[A] = iterator().headOption /** The number of elements in this collection, if it can be cheaply computed, * -1 otherwise. Cheaply usually means: Not requiring a collection traversal. @@ -165,8 +164,6 @@ trait IterableMonoTransforms[+A, +Repr] extends Any { */ def drop(n: Int): Repr = fromIterableWithSameElemType(View.Drop(coll, n)) - /** The rest of the collection without its first element. */ - def tail: Repr = drop(1) } /** Transforms over iterables that can return collections of different element types. diff --git a/src/main/scala/strawman/collection/Seq.scala b/src/main/scala/strawman/collection/Seq.scala index f28a9849f0..09b6cd78bc 100644 --- a/src/main/scala/strawman/collection/Seq.scala +++ b/src/main/scala/strawman/collection/Seq.scala @@ -1,6 +1,6 @@ package strawman.collection -import scala.{Any, Boolean, Int, IndexOutOfBoundsException} +import scala.{Any, Boolean, Int, IndexOutOfBoundsException, Option, Some, None} import strawman.collection.mutable.Iterator import strawman.collection.immutable.{List, Nil} @@ -9,22 +9,21 @@ import scala.annotation.unchecked.uncheckedVariance /** Base trait for sequence collections */ trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] with ArrayLike[A] +trait InhabitedSeq[+A] + extends Seq[A] + with InhabitedLinearSeqOps[A, Seq[A]] + /** Base trait for linearly accessed sequences that have efficient `head` and * `tail` operations. * Known subclasses: List, LazyList */ trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self => - /** To be overridden in implementations: */ - def isEmpty: Boolean - def head: A - def tail: LinearSeq[A] - /** `iterator` is overridden in terms of `head` and `tail` */ def iterator() = new Iterator[A] { - private[this] var current: Seq[A] = self + private[this] var current: LinearSeq[A] = self def hasNext = !current.isEmpty - def next() = { val r = current.head; current = current.tail; r } + def next() = { val r = current.unsafeHead; current = current.unsafeTail; r } } /** `length` is defined in terms of `iterator` */ @@ -37,10 +36,23 @@ trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self => if (n < 0) throw new IndexOutOfBoundsException(n.toString) val skipped = drop(n) if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString) - skipped.head + skipped.unsafeHead } } +/** + * Collections that have at least one `head` element followed + * by a `tail`. + */ +trait InhabitedLinearSeqOps[+A, +Repr] extends Any { + + /** The first element of the collection. */ + def head: A + + /** The rest of the collection without its first element. */ + def tail: Repr +} + trait IndexedSeq[+A] extends Seq[A] { self => override def view: IndexedView[A] = new IndexedView[A] { def length: Int = self.length @@ -54,8 +66,21 @@ trait SeqLike[+A, +C[X] <: Seq[X]] extends IterableLike[A, C] with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote +trait InhabitedLinearSeqFactory[+C[X] <: InhabitedSeq[X]] { + def apply[A](a: A, as: A*): C[A] +} + /** Base trait for linear Seq operations */ -trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] { +trait LinearSeqLike[+A, +C[+X] <: LinearSeq[X]] extends SeqLike[A, C] { + + protected def coll: C[A] + + /** Extract the head and tail, if the collection is not empty */ + def uncons: Option[(A, C[A])] + + /** To be overriden for performance in subclasses */ + protected def unsafeHead: A = uncons.get._1 + protected def unsafeTail: C[A] = uncons.get._2 /** Optimized version of `drop` that avoids copying * Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`, @@ -63,15 +88,16 @@ trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] { * whereas we need to assume here that `Repr` is the same as the underlying * collection type. */ - override def drop(n: Int): C[A @uncheckedVariance] = { // sound bcs of VarianceNote - def loop(n: Int, s: Iterable[A]): C[A] = - if (n <= 0) s.asInstanceOf[C[A]] - // implicit contract to guarantee success of asInstanceOf: - // (1) coll is of type C[A] - // (2) The tail of a LinearSeq is of the same type as the type of the sequence itself - // it's surprisingly tricky/ugly to turn this into actual types, so we - // leave this contract implicit. - else loop(n - 1, s.tail) + override def drop(n: Int): C[A] = { + def loop(n: Int, s: C[A]): C[A] = { + if (n <= 0) s + // implicit contract to guarantee success of asInstanceOf: + // (1) coll is of type C[A] + // (2) The tail of a LinearSeq is of the same type as the type of the sequence itself + // it's surprisingly tricky/ugly to turn this into actual types, so we + // leave this contract implicit. + else loop(n - 1, s.unsafeTail.asInstanceOf[C[A]]) + } loop(n, coll) } } diff --git a/src/main/scala/strawman/collection/immutable/LazyList.scala b/src/main/scala/strawman/collection/immutable/LazyList.scala index b9cf342948..998f28d9be 100644 --- a/src/main/scala/strawman/collection/immutable/LazyList.scala +++ b/src/main/scala/strawman/collection/immutable/LazyList.scala @@ -18,8 +18,6 @@ class LazyList[+A](expr: => LazyList.Evaluated[A]) } override def isEmpty = force.isEmpty - override def head = force.get._1 - override def tail = force.get._2 def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this))) @@ -34,6 +32,8 @@ class LazyList[+A](expr: => LazyList.Evaluated[A]) case Some((hd, tl)) => s"$hd #:: $tl" } else "LazyList(?)" + + def uncons: Option[(A, LazyList[A])] = force } object LazyList extends IterableFactory[LazyList] { diff --git a/src/main/scala/strawman/collection/immutable/List.scala b/src/main/scala/strawman/collection/immutable/List.scala index 623062dde8..56673facec 100644 --- a/src/main/scala/strawman/collection/immutable/List.scala +++ b/src/main/scala/strawman/collection/immutable/List.scala @@ -1,16 +1,16 @@ package strawman.collection.immutable import scala.annotation.unchecked.uncheckedVariance -import scala.Nothing +import scala.{Option, None, NoSuchElementException, Nothing, Some, UnsupportedOperationException} import scala.Predef.??? -import strawman.collection.{Iterable, IterableFactory, IterableOnce, LinearSeq, SeqLike} +import strawman.collection.{InhabitedLinearSeqFactory, InhabitedLinearSeqOps, InhabitedSeq, Iterable, IterableFactory, IterableOnce, LinearSeq, LinearSeqLike, View} import strawman.collection.mutable.{Buildable, ListBuffer} /** Concrete collection type: List */ sealed trait List[+A] extends LinearSeq[A] - with SeqLike[A, List] + with LinearSeqLike[A, List] with Buildable[A, List[A]] { def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) @@ -18,12 +18,12 @@ sealed trait List[+A] protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList) /** Prepend element */ - def :: [B >: A](elem: B): List[B] = new ::(elem, this) + def :: [B >: A](elem: B): List.NonEmpty[B] = new ::(elem, this) /** Prepend operation that avoids copying this list */ def ++:[B >: A](prefix: List[B]): List[B] = if (prefix.isEmpty) this - else prefix.head :: prefix.tail ++: this + else prefix.unsafeHead :: (prefix.unsafeTail ++: this) /** When concatenating with another list `xs`, avoid copying `xs` */ override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { @@ -35,21 +35,37 @@ sealed trait List[+A] } case class :: [+A](x: A, private[collection] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally - extends List[A] { + extends List[A] + with InhabitedSeq[A] + with InhabitedLinearSeqOps[A, List[A]] { + override def isEmpty = false - override def head = x - override def tail = next + def head = x + def tail = next + + def uncons: Option[(A, List[A])] = Some((x, next)) + override protected def unsafeHead: A = x + override protected def unsafeTail: List[A] = next } case object Nil extends List[Nothing] { override def isEmpty = true - override def head = ??? - override def tail = ??? + + def uncons = None + override protected def unsafeHead: Nothing = throw new NoSuchElementException + override protected def unsafeTail: Nothing = throw new UnsupportedOperationException } -object List extends IterableFactory[List] { +object List extends IterableFactory[List] with InhabitedLinearSeqFactory[::] { + + type NonEmpty[A] = ::[A] + def fromIterable[B](coll: Iterable[B]): List[B] = coll match { case coll: List[B] => coll case _ => ListBuffer.fromIterable(coll).toList } + + def apply[A](a: A, as: A*): List.NonEmpty[A] = + a :: fromIterable(View.Elems(as: _*)) + } \ No newline at end of file diff --git a/src/main/scala/strawman/collection/mutable/Iterator.scala b/src/main/scala/strawman/collection/mutable/Iterator.scala index 51ab5c7cba..a04dfa2aee 100644 --- a/src/main/scala/strawman/collection/mutable/Iterator.scala +++ b/src/main/scala/strawman/collection/mutable/Iterator.scala @@ -1,6 +1,6 @@ package strawman.collection.mutable -import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException} +import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException, Option, Some, None} import strawman.collection.{IndexedView, IterableOnce} /** A core Iterator class */ @@ -26,6 +26,9 @@ trait Iterator[+A] { self => while (hasNext) { len += 1; next() } len } + + def headOption: Option[A] = if (hasNext) Some(next()) else None + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { private var hd: A = _ private var hdDefined: Boolean = false diff --git a/src/test/scala/strawman/collection/test/Test.scala b/src/test/scala/strawman/collection/test/Test.scala index 5b7922a982..392868d864 100644 --- a/src/test/scala/strawman/collection/test/Test.scala +++ b/src/test/scala/strawman/collection/test/Test.scala @@ -11,7 +11,7 @@ import org.junit.Test class StrawmanTest { - def seqOps(xs: Seq[Int]): Unit = { + def seqOps(xs: InhabitedSeq[Int]): Unit = { val x1 = xs.foldLeft("")(_ + _) val y1: String = x1 val x2 = xs.foldRight("")(_ + _) @@ -69,8 +69,6 @@ class StrawmanTest { val y2: String = x2 val x3 = xs.indexWhere(_ % 2 == 0) val y3: Int = x3 - val x4 = xs.head - val y4: Int = x4 val x5 = xs.to(List) val y5: List[Int] = x5 val (xs6, xs7) = xs.partition(_ % 2 == 0) @@ -96,7 +94,6 @@ class StrawmanTest { println(x1) println(x2) println(x3) - println(x4) println(x5) println(xs6.to(List)) println(xs7.to(List)) @@ -117,8 +114,6 @@ class StrawmanTest { val y2: String = x2 val x3 = xs.indexWhere(_ % 2 == 0) val y3: Int = x3 - val x4 = xs.head - val y4: Int = x4 val x5 = xs.to(List) val y5: List[Char] = x5 val (xs6, xs7) = xs.partition(_ % 2 == 0) @@ -148,7 +143,6 @@ class StrawmanTest { println(x1) println(x2) println(x3) - println(x4) println(x5) println(xs6) println(xs7) @@ -171,8 +165,6 @@ class StrawmanTest { val y2: String = x2 val x3 = xs.indexWhere(_ % 2 == 0) val y3: Int = x3 - val x4 = xs.head - val y4: Int = x4 val x5 = xs.to(List) val y5: List[Int] = x5 val (xs6, xs7) = xs.partition(_ % 2 == 0) @@ -200,7 +192,6 @@ class StrawmanTest { println(x1) println(x2) println(x3) - println(x4) println(x5) println(xs6.view) println(xs7.view) @@ -222,8 +213,6 @@ class StrawmanTest { val y2: String = x2 val x3 = xs.indexWhere(_ % 2 == 0) val y3: Int = x3 - val x4 = xs.head - val y4: Int = x4 val x5 = xs.to(List) val y5: List[Int] = x5 val (xs6, xs7) = xs.partition(_ % 2 == 0) @@ -251,7 +240,6 @@ class StrawmanTest { println(x1) println(x2) println(x3) - println(x4) println(x5) println(xs6) println(xs6.to(List)) @@ -295,8 +283,8 @@ class StrawmanTest { val intsListBuf = ints.to(ListBuffer) val intsView = ints.view seqOps(ints) - seqOps(intsBuf) - seqOps(intsListBuf) +// seqOps(intsBuf) +// seqOps(intsListBuf) viewOps(intsView) stringOps("abc") arrayOps(Array(1, 2, 3))