From 8f5f03c25f65b63d2aeca208e519975fee54e2a9 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Tue, 20 Dec 2016 10:57:39 +0100 Subject: [PATCH] Split code to several files. Introduce strawman.collection.mutable and strawman.collection.immutable packages. Seq is still in strawman.collection package because it is implemented by both mutable (e.g. ArrayBuffer) and immutable (e.g. List) concrete collections. --- build.sbt | 2 +- .../collection/CollectionStrawman6.scala | 708 ------------------ .../scala/strawman/collection/Iterable.scala | 190 +++++ .../strawman/collection/IterableOnce.scala | 8 + src/main/scala/strawman/collection/Seq.scala | 95 +++ src/main/scala/strawman/collection/View.scala | 9 +- .../collection/immutable/LazyList.scala | 56 ++ .../strawman/collection/immutable/List.scala | 55 ++ .../strawman/collection/javaSupport.scala | 112 +++ .../collection/mutable/ArrayBuffer.scala | 93 +++ .../strawman/collection/mutable/Builder.scala | 61 ++ .../collection/mutable/Iterator.scala | 111 +++ .../collection/mutable/ListBuffer.scala | 61 ++ .../scala/strawman/collection/package.scala | 103 +-- .../scala/strawman/collection/test/Test.scala | 11 +- 15 files changed, 865 insertions(+), 810 deletions(-) delete mode 100644 src/main/scala/strawman/collection/CollectionStrawman6.scala create mode 100644 src/main/scala/strawman/collection/Iterable.scala create mode 100644 src/main/scala/strawman/collection/IterableOnce.scala create mode 100644 src/main/scala/strawman/collection/Seq.scala create mode 100644 src/main/scala/strawman/collection/immutable/LazyList.scala create mode 100644 src/main/scala/strawman/collection/immutable/List.scala create mode 100644 src/main/scala/strawman/collection/javaSupport.scala create mode 100644 src/main/scala/strawman/collection/mutable/ArrayBuffer.scala create mode 100644 src/main/scala/strawman/collection/mutable/Builder.scala create mode 100644 src/main/scala/strawman/collection/mutable/Iterator.scala create mode 100644 src/main/scala/strawman/collection/mutable/ListBuffer.scala diff --git a/build.sbt b/build.sbt index 2a884e39d1..4f3a8b7356 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ version := "0.1-SNAPSHOT" scalaVersion := "2.12.1" -scalacOptions ++= Seq("-deprecation", "-unchecked") +scalacOptions ++= Seq("-deprecation", "-unchecked", "-Yno-imports", "-language:higherKinds") testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v", "-s", "-a") diff --git a/src/main/scala/strawman/collection/CollectionStrawman6.scala b/src/main/scala/strawman/collection/CollectionStrawman6.scala deleted file mode 100644 index b77a4831df..0000000000 --- a/src/main/scala/strawman/collection/CollectionStrawman6.scala +++ /dev/null @@ -1,708 +0,0 @@ -package strawman.collection - -import Predef.{augmentString => _, wrapString => _, _} -import scala.reflect.ClassTag -import annotation.unchecked.uncheckedVariance -import annotation.tailrec -import scala.language.higherKinds - -/* ------------ Base Traits -------------------------------- */ - -/** Iterator can be used only once */ -trait IterableOnce[+A] { - def iterator(): Iterator[A] -} - -/** Base trait for instances that can construct a collection from an iterable */ -trait FromIterable[+C[X] <: Iterable[X]] { - def fromIterable[B](it: Iterable[B]): C[B] -} - -/** Base trait for companion objects of collections */ -trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { - def empty[X]: C[X] = fromIterable(View.Empty) - def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) -} - -/** Base trait for generic collections */ -trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { - /** The collection itself */ - protected def coll: this.type = this -} - -/** A trait representing indexable collections with finite length */ -trait ArrayLike[+A] extends Any { - def length: Int - def apply(i: Int): A -} - -/** Base trait for sequence collections */ -trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] with ArrayLike[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 - def hasNext = !current.isEmpty - def next() = { val r = current.head; current = current.tail; r } - } - - /** `length` is defined in terms of `iterator` */ - def length: Int = iterator().length - - /** `apply` is defined in terms of `drop`, which is in turn defined in - * terms of `tail`. - */ - override def apply(n: Int): A = { - if (n < 0) throw new IndexOutOfBoundsException(n.toString) - val skipped = drop(n) - if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString) - skipped.head - } -} - -trait IndexedSeq[+A] extends Seq[A] { self => - override def view: IndexedView[A] = new IndexedView[A] { - def length: Int = self.length - def apply(i: Int): A = self(i) - } -} - -/** Base trait for strict collections that can be built using a builder. - * @tparam A the element type of the collection - * @tparam Repr the type of the underlying collection - */ -trait Buildable[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { - - /** Creates a new builder. */ - protected[this] def newBuilder: Builder[A, Repr] - - /** Optimized, push-based version of `partition`. */ - override def partition(p: A => Boolean): (Repr, Repr) = { - val l, r = newBuilder - coll.iterator().foreach(x => (if (p(x)) l else r) += x) - (l.result, r.result) - } - - // one might also override other transforms here to avoid generating - // iterators if it helps efficiency. -} - -/** Base trait for collection builders */ -trait Builder[-A, +To] { self => - - /** Append an element */ - def +=(x: A): this.type - - /** Result collection consisting of all elements appended so far. */ - def result: To - - /** Bulk append. Can be overridden if specialized implementations are available. */ - def ++=(xs: IterableOnce[A]): this.type = { - xs.iterator().foreach(+=) - this - } - - /** A builder resulting from this builder my mapping the result using `f`. */ - def mapResult[NewTo](f: To => NewTo) = new Builder[A, NewTo] { - def +=(x: A): this.type = { self += x; this } - override def ++=(xs: IterableOnce[A]): this.type = { self ++= xs; this } - def result: NewTo = f(self.result) - } -} - -/* ------------ Operations ----------------------------------- */ - -/** Base trait for Iterable operations - * - * VarianceNote - * ============ - * - * We require that for all child classes of Iterable the variance of - * the child class and the variance of the `C` parameter passed to `IterableLike` - * are the same. We cannot express this since we lack variance polymorphism. That's - * why we have to resort at some places to write `C[A @uncheckedVariance]`. - * - */ -trait IterableLike[+A, +C[X] <: Iterable[X]] - extends FromIterable[C] - with IterableOps[A] - with IterableMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote - with IterablePolyTransforms[A, C] { - - /** Create a collection of type `C[A]` from the elements of `coll`, which has - * the same element type as this collection. Overridden in StringOps and ArrayOps. - */ - protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) -} - -/** Base trait for Seq operations */ -trait SeqLike[+A, +C[X] <: Seq[X]] -extends IterableLike[A, C] - with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote - -/** Base trait for linear Seq operations */ -trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] { - - /** Optimized version of `drop` that avoids copying - * Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`, - * because the `...MonoTransforms` traits make no assumption about the type of `Repr` - * 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) - loop(n, coll) - } -} - -/** Operations over iterables. No operation defined here is generic in the - * type of the underlying collection. - */ -trait IterableOps[+A] extends Any { - protected def coll: Iterable[A] - private def iterator() = coll.iterator() - - /** Apply `f` to each element for tis side effects */ - def foreach(f: A => Unit): Unit = iterator().foreach(f) - - /** Fold left */ - def foldLeft[B](z: B)(op: (B, A) => B): B = iterator().foldLeft(z)(op) - - /** Fold right */ - def foldRight[B](z: B)(op: (A, B) => B): B = iterator().foldRight(z)(op) - - /** The index of the first element in this collection for which `p` holds. */ - def indexWhere(p: A => Boolean): Int = iterator().indexWhere(p) - - /** Is the collection empty? */ - def isEmpty: Boolean = !iterator().hasNext - - /** The first element of the collection. */ - def head: A = iterator().next() - - /** The number of elements in this collection, if it can be cheaply computed, - * -1 otherwise. Cheaply usually means: Not requiring a collection traversal. - */ - def knownSize: Int = -1 - - /** The number of elements in this collection. Does not terminate for - * infinite collections. - */ - def size: Int = if (knownSize >= 0) knownSize else iterator().length - - /** A view representing the elements of this collection. */ - def view: View[A] = View.fromIterator(iterator()) - - /** Given a collection factory `fi` for collections of type constructor `C`, - * convert this collection to one of type `C[A]`. Example uses: - * - * xs.to(List) - * xs.to(ArrayBuffer) - */ - def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A @uncheckedVariance] = - // variance seems sound because `to` could just as well have been added - // as a decorator. We should investigate this further to be sure. - fi.fromIterable(coll) - - /** Convert collection to array. */ - def toArray[B >: A: ClassTag]: Array[B] = - if (knownSize >= 0) copyToArray(new Array[B](knownSize), 0) - else ArrayBuffer.fromIterable(coll).toArray[B] - - /** Copy all elements of this collection to array `xs`, starting at `start`. */ - def copyToArray[B >: A](xs: Array[B], start: Int = 0): xs.type = { - var i = start - val it = iterator() - while (it.hasNext) { - xs(i) = it.next() - i += 1 - } - xs - } - - /** The class name of this collection. To be used for converting to string. - * Collections generally print like this: - * - * (elem_1, ..., elem_n) - */ - def className = getClass.getName - - /** A string showing all elements of this collection, separated by string `sep`. */ - def mkString(sep: String): String = { - var first: Boolean = true - val b = new StringBuilder() - foreach { elem => - if (!first) b ++= sep - first = false - b ++= String.valueOf(elem) - } - b.result - } - - override def toString = s"$className(${mkString(", ")})" -} - -/** Type-preserving transforms over iterables. - * Operations defined here return in their result iterables of the same type - * as the one they are invoked on. - */ -trait IterableMonoTransforms[+A, +Repr] extends Any { - protected def coll: Iterable[A] - protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): Repr - - /** All elements satisfying predicate `p` */ - def filter(p: A => Boolean): Repr = fromIterableWithSameElemType(View.Filter(coll, p)) - - /** A pair of, first, all elements that satisfy prediacte `p` and, second, - * all elements that do not. Interesting because it splits a collection in two. - * - * The default implementation provided here needs to traverse the collection twice. - * Strict collections have an overridden version of `partition` in `Buildable`, - * which requires only a single traversal. - */ - def partition(p: A => Boolean): (Repr, Repr) = { - val pn = View.Partition(coll, p) - (fromIterableWithSameElemType(pn.left), fromIterableWithSameElemType(pn.right)) - } - - /** A collection containing the first `n` elements of this collection. */ - def take(n: Int): Repr = fromIterableWithSameElemType(View.Take(coll, n)) - - /** The rest of the collection without its `n` first elements. For - * linear, immutable collections this should avoid making a copy. - */ - 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. - */ -trait IterablePolyTransforms[+A, +C[A]] extends Any { - protected def coll: Iterable[A] - def fromIterable[B](coll: Iterable[B]): C[B] - - /** Map */ - def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) - - /** Flatmap */ - def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) - - /** Concatenation */ - def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) - - /** Zip. Interesting because it requires to align to source collections. */ - def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) - // sound bcs of VarianceNote -} - -/** Type-preserving transforms over sequences. */ -trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { - def reverse: Repr = coll.view match { - case v: IndexedView[A] => fromIterableWithSameElemType(v.reverse) - case _ => - var xs: List[A] = Nil - var it = coll.iterator() - while (it.hasNext) xs = it.next() :: xs - fromIterableWithSameElemType(xs) - } -} - -/* --------- Concrete collection types ------------------------------- */ - -/** Concrete collection type: List */ -sealed trait List[+A] -extends LinearSeq[A] - with SeqLike[A, List] - with Buildable[A, List[A]] { - - def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) - - protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList) - - /** Prepend element */ - def :: [B >: A](elem: B): List[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 - - /** When concatenating with another list `xs`, avoid copying `xs` */ - override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { - case xs: List[B] => this ++: xs - case _ => super.++(xs) - } - - override def className = "List" -} - -case class :: [+A](x: A, private[collection] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally -extends List[A] { - override def isEmpty = false - override def head = x - override def tail = next -} - -case object Nil extends List[Nothing] { - override def isEmpty = true - override def head = ??? - override def tail = ??? -} - -object List extends IterableFactory[List] { - def fromIterable[B](coll: Iterable[B]): List[B] = coll match { - case coll: List[B] => coll - case _ => ListBuffer.fromIterable(coll).toList - } -} - -/** Concrete collection type: ListBuffer */ -class ListBuffer[A] -extends Seq[A] - with SeqLike[A, ListBuffer] - with Buildable[A, ListBuffer[A]] - with Builder[A, ListBuffer[A]] { - - private var first, last: List[A] = Nil - private var aliased = false - private var len = 0 - - def iterator() = first.iterator() - - def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) - - def apply(i: Int) = first.apply(i) - - def length = len - override def knownSize = len - - protected[this] def newBuilder = new ListBuffer[A] - - private def copyElems(): Unit = { - val buf = ListBuffer.fromIterable(result) - first = buf.first - last = buf.last - aliased = false - } - - /** Convert to list; avoids copying where possible. */ - def toList = { - aliased = true - first - } - - def +=(elem: A) = { - if (aliased) copyElems() - val last1 = elem :: Nil - last match { - case last: ::[A] => last.next = last1 - case _ => first = last1 - } - last = last1 - len += 1 - this - } - - def result = this - - override def className = "ListBuffer" -} - -object ListBuffer extends IterableFactory[ListBuffer] { - def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = new ListBuffer[B] ++= coll -} - -/** Concrete collection type: ArrayBuffer */ -class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) -extends Seq[A] - with SeqLike[A, ArrayBuffer] - with Buildable[A, ArrayBuffer[A]] - with Builder[A, ArrayBuffer[A]] { - - def this() = this(new Array[AnyRef](16), 0) - - private var elems: Array[AnyRef] = initElems - private var start = 0 - private var end = initLength - - def apply(n: Int) = elems(start + n).asInstanceOf[A] - - def length = end - start - override def knownSize = length - - override def view = new ArrayBufferView(elems, start, end) - - def iterator() = view.iterator() - - def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = - ArrayBuffer.fromIterable(it) - - protected[this] def newBuilder = new ArrayBuffer[A] - - def +=(elem: A): this.type = { - if (end == elems.length) { - if (start > 0) { - Array.copy(elems, start, elems, 0, length) - end -= start - start = 0 - } - else { - val newelems = new Array[AnyRef](end * 2) - Array.copy(elems, 0, newelems, 0, end) - elems = newelems - } - } - elems(end) = elem.asInstanceOf[AnyRef] - end += 1 - this - } - - def result = this - - /** New operation: destructively drop elements at start of buffer. */ - def trimStart(n: Int): Unit = start += (n max 0) - - /** Overridden to use array copying for efficiency where possible. */ - override def ++[B >: A](xs: IterableOnce[B]): ArrayBuffer[B] = xs match { - case xs: ArrayBuffer[B] => - val elems = new Array[AnyRef](length + xs.length) - Array.copy(this.elems, this.start, elems, 0, this.length) - Array.copy(xs.elems, xs.start, elems, this.length, xs.length) - new ArrayBuffer(elems, elems.length) - case _ => super.++(xs) - } - - override def take(n: Int) = { - val elems = new Array[AnyRef](n min length) - Array.copy(this.elems, this.start, elems, 0, elems.length) - new ArrayBuffer(elems, elems.length) - } - - override def className = "ArrayBuffer" -} - -object ArrayBuffer extends IterableFactory[ArrayBuffer] { - - /** Avoid reallocation of buffer if length is known. */ - def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = - if (coll.knownSize >= 0) { - val elems = new Array[AnyRef](coll.knownSize) - val it = coll.iterator() - for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] - new ArrayBuffer[B](elems, elems.length) - } - else new ArrayBuffer[B] ++= coll -} - -class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { - def length = end - start - def apply(n: Int) = elems(start + n).asInstanceOf[A] - override def className = "ArrayBufferView" -} - -class LazyList[+A](expr: => LazyList.Evaluated[A]) -extends LinearSeq[A] with SeqLike[A, LazyList] { - private[this] var evaluated = false - private[this] var result: LazyList.Evaluated[A] = _ - - def force: LazyList.Evaluated[A] = { - if (!evaluated) { - result = expr - evaluated = true - } - result - } - - 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))) - - def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) - - override def className = "LazyList" - - override def toString = - if (evaluated) - result match { - case None => "Empty" - case Some((hd, tl)) => s"$hd #:: $tl" - } - else "LazyList(?)" -} - -object LazyList extends IterableFactory[LazyList] { - - type Evaluated[+A] = Option[(A, LazyList[A])] - - object Empty extends LazyList[Nothing](None) - - object #:: { - def unapply[A](s: LazyList[A]): Evaluated[A] = s.force - } - - def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { - case coll: LazyList[B] => coll - case _ => fromIterator(coll.iterator()) - } - - def fromIterator[B](it: Iterator[B]): LazyList[B] = - new LazyList(if (it.hasNext) Some(it.next(), fromIterator(it)) else None) -} - -// ------------------ String and Array support ----------------------- - -class StringBuilder extends Builder[Char, String] { - private val sb = new java.lang.StringBuilder - - def += (x: Char) = { sb.append(x); this } - - /** Overloaded version of `++=` that takes a string */ - def ++= (s: String) = { sb.append(s); this } - - def result = sb.toString - - override def toString = result -} - -case class StringView(s: String) extends IndexedView[Char] { - def length = s.length - def apply(n: Int) = s.charAt(n) - override def className = "StringView" -} - -case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { - def length = xs.length - def apply(n: Int) = xs(n) - override def className = "ArrayView" -} - -/* ---------- Iterators ---------------------------------------------------*/ - -/** A core Iterator class */ -trait Iterator[+A] { self => - def hasNext: Boolean - def next(): A - def foldLeft[B](z: B)(op: (B, A) => B): B = - if (hasNext) foldLeft(op(z, next()))(op) else z - def foldRight[B](z: B)(op: (A, B) => B): B = - if (hasNext) op(next(), foldRight(z)(op)) else z - def foreach(f: A => Unit): Unit = - while (hasNext) f(next()) - def indexWhere(p: A => Boolean): Int = { - var i = 0 - while (hasNext) { - if (p(next())) return i - i += 1 - } - -1 - } - def length = { - var len = 0 - while (hasNext) { len += 1; next() } - len - } - def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { - private var hd: A = _ - private var hdDefined: Boolean = false - - def hasNext: Boolean = hdDefined || { - do { - if (!self.hasNext) return false - hd = self.next() - } while (!p(hd)) - hdDefined = true - true - } - - def next() = - if (hasNext) { - hdDefined = false - hd - } - else Iterator.empty.next() - } - def map[B](f: A => B): Iterator[B] = new Iterator[B] { - def hasNext = self.hasNext - def next() = f(self.next()) - } - - def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { - private var myCurrent: Iterator[B] = Iterator.empty - private def current = { - while (!myCurrent.hasNext && self.hasNext) - myCurrent = f(self.next()).iterator() - myCurrent - } - def hasNext = current.hasNext - def next() = current.next() - } - def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { - private var myCurrent: Iterator[B] = self - private var first = true - private def current = { - if (!myCurrent.hasNext && first) { - myCurrent = xs.iterator() - first = false - } - myCurrent - } - def hasNext = current.hasNext - def next() = current.next() - } - def take(n: Int): Iterator[A] = new Iterator[A] { - private var i = 0 - def hasNext = self.hasNext && i < n - def next() = - if (hasNext) { - i += 1 - self.next() - } - else Iterator.empty.next() - } - def drop(n: Int): Iterator[A] = { - var i = 0 - while (i < n && hasNext) { - next() - i += 1 - } - this - } - def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { - val thatIterator = that.iterator() - def hasNext = self.hasNext && thatIterator.hasNext - def next() = (self.next(), thatIterator.next()) - } -} - -object Iterator { - val empty: Iterator[Nothing] = new Iterator[Nothing] { - def hasNext = false - def next() = throw new NoSuchElementException("next on empty iterator") - } - def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { - val length = xs.length - def apply(n: Int) = xs(n) - }.iterator() -} diff --git a/src/main/scala/strawman/collection/Iterable.scala b/src/main/scala/strawman/collection/Iterable.scala new file mode 100644 index 0000000000..079adcda33 --- /dev/null +++ b/src/main/scala/strawman/collection/Iterable.scala @@ -0,0 +1,190 @@ +package strawman.collection + +import scala.annotation.unchecked.uncheckedVariance +import scala.reflect.ClassTag +import scala.{Int, Boolean, Array, Any, Unit, StringContext} +import java.lang.String + +import strawman.collection.mutable.{ArrayBuffer, StringBuilder} + +/** Base trait for generic collections */ +trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { + /** The collection itself */ + protected def coll: this.type = this +} + +/** Base trait for Iterable operations + * + * VarianceNote + * ============ + * + * We require that for all child classes of Iterable the variance of + * the child class and the variance of the `C` parameter passed to `IterableLike` + * are the same. We cannot express this since we lack variance polymorphism. That's + * why we have to resort at some places to write `C[A @uncheckedVariance]`. + * + */ +trait IterableLike[+A, +C[X] <: Iterable[X]] + extends FromIterable[C] + with IterableOps[A] + with IterableMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + with IterablePolyTransforms[A, C] { + + /** Create a collection of type `C[A]` from the elements of `coll`, which has + * the same element type as this collection. Overridden in StringOps and ArrayOps. + */ + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) +} + +/** Base trait for instances that can construct a collection from an iterable */ +trait FromIterable[+C[X] <: Iterable[X]] { + def fromIterable[B](it: Iterable[B]): C[B] +} + +/** Base trait for companion objects of collections */ +trait IterableFactory[+C[X] <: Iterable[X]] extends FromIterable[C] { + def empty[X]: C[X] = fromIterable(View.Empty) + def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) +} + +/** Operations over iterables. No operation defined here is generic in the + * type of the underlying collection. + */ +trait IterableOps[+A] extends Any { + protected def coll: Iterable[A] + private def iterator() = coll.iterator() + + /** Apply `f` to each element for tis side effects */ + def foreach(f: A => Unit): Unit = iterator().foreach(f) + + /** Fold left */ + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator().foldLeft(z)(op) + + /** Fold right */ + def foldRight[B](z: B)(op: (A, B) => B): B = iterator().foldRight(z)(op) + + /** The index of the first element in this collection for which `p` holds. */ + def indexWhere(p: A => Boolean): Int = iterator().indexWhere(p) + + /** Is the collection empty? */ + def isEmpty: Boolean = !iterator().hasNext + + /** The first element of the collection. */ + def head: A = iterator().next() + + /** The number of elements in this collection, if it can be cheaply computed, + * -1 otherwise. Cheaply usually means: Not requiring a collection traversal. + */ + def knownSize: Int = -1 + + /** The number of elements in this collection. Does not terminate for + * infinite collections. + */ + def size: Int = if (knownSize >= 0) knownSize else iterator().length + + /** A view representing the elements of this collection. */ + def view: View[A] = View.fromIterator(iterator()) + + /** Given a collection factory `fi` for collections of type constructor `C`, + * convert this collection to one of type `C[A]`. Example uses: + * + * xs.to(List) + * xs.to(ArrayBuffer) + */ + def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A @uncheckedVariance] = + // variance seems sound because `to` could just as well have been added + // as a decorator. We should investigate this further to be sure. + fi.fromIterable(coll) + + /** Convert collection to array. */ + def toArray[B >: A: ClassTag]: Array[B] = + if (knownSize >= 0) copyToArray(new Array[B](knownSize), 0) + else ArrayBuffer.fromIterable(coll).toArray[B] + + /** Copy all elements of this collection to array `xs`, starting at `start`. */ + def copyToArray[B >: A](xs: Array[B], start: Int = 0): xs.type = { + var i = start + val it = iterator() + while (it.hasNext) { + xs(i) = it.next() + i += 1 + } + xs + } + + /** The class name of this collection. To be used for converting to string. + * Collections generally print like this: + * + * (elem_1, ..., elem_n) + */ + def className = getClass.getName + + /** A string showing all elements of this collection, separated by string `sep`. */ + def mkString(sep: String): String = { + var first: Boolean = true + val b = new StringBuilder() + foreach { elem => + if (!first) b ++= sep + first = false + b ++= String.valueOf(elem) + } + b.result + } + + override def toString = s"$className(${mkString(", ")})" +} + +/** Type-preserving transforms over iterables. + * Operations defined here return in their result iterables of the same type + * as the one they are invoked on. + */ +trait IterableMonoTransforms[+A, +Repr] extends Any { + protected def coll: Iterable[A] + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): Repr + + /** All elements satisfying predicate `p` */ + def filter(p: A => Boolean): Repr = fromIterableWithSameElemType(View.Filter(coll, p)) + + /** A pair of, first, all elements that satisfy prediacte `p` and, second, + * all elements that do not. Interesting because it splits a collection in two. + * + * The default implementation provided here needs to traverse the collection twice. + * Strict collections have an overridden version of `partition` in `Buildable`, + * which requires only a single traversal. + */ + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromIterableWithSameElemType(pn.left), fromIterableWithSameElemType(pn.right)) + } + + /** A collection containing the first `n` elements of this collection. */ + def take(n: Int): Repr = fromIterableWithSameElemType(View.Take(coll, n)) + + /** The rest of the collection without its `n` first elements. For + * linear, immutable collections this should avoid making a copy. + */ + 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. + */ +trait IterablePolyTransforms[+A, +C[A]] extends Any { + protected def coll: Iterable[A] + def fromIterable[B](coll: Iterable[B]): C[B] + + /** Map */ + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + + /** Flatmap */ + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + + /** Concatenation */ + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + + /** Zip. Interesting because it requires to align to source collections. */ + def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote +} diff --git a/src/main/scala/strawman/collection/IterableOnce.scala b/src/main/scala/strawman/collection/IterableOnce.scala new file mode 100644 index 0000000000..be034b04d4 --- /dev/null +++ b/src/main/scala/strawman/collection/IterableOnce.scala @@ -0,0 +1,8 @@ +package strawman.collection + +import strawman.collection.mutable.Iterator + +trait IterableOnce[+A] { + /** Iterator can be used only once */ + def iterator(): Iterator[A] +} \ No newline at end of file diff --git a/src/main/scala/strawman/collection/Seq.scala b/src/main/scala/strawman/collection/Seq.scala new file mode 100644 index 0000000000..f28a9849f0 --- /dev/null +++ b/src/main/scala/strawman/collection/Seq.scala @@ -0,0 +1,95 @@ +package strawman.collection + +import scala.{Any, Boolean, Int, IndexOutOfBoundsException} +import strawman.collection.mutable.Iterator +import strawman.collection.immutable.{List, Nil} + +import scala.annotation.unchecked.uncheckedVariance + +/** Base trait for sequence collections */ +trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] with ArrayLike[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 + def hasNext = !current.isEmpty + def next() = { val r = current.head; current = current.tail; r } + } + + /** `length` is defined in terms of `iterator` */ + def length: Int = iterator().length + + /** `apply` is defined in terms of `drop`, which is in turn defined in + * terms of `tail`. + */ + override def apply(n: Int): A = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val skipped = drop(n) + if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString) + skipped.head + } +} + +trait IndexedSeq[+A] extends Seq[A] { self => + override def view: IndexedView[A] = new IndexedView[A] { + def length: Int = self.length + def apply(i: Int): A = self(i) + } +} + + +/** Base trait for Seq operations */ +trait SeqLike[+A, +C[X] <: Seq[X]] + extends IterableLike[A, C] + with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + +/** Base trait for linear Seq operations */ +trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] { + + /** Optimized version of `drop` that avoids copying + * Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`, + * because the `...MonoTransforms` traits make no assumption about the type of `Repr` + * 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) + loop(n, coll) + } +} + +/** Type-preserving transforms over sequences. */ +trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + def reverse: Repr = coll.view match { + case v: IndexedView[A] => fromIterableWithSameElemType(v.reverse) + case _ => + var xs: List[A] = Nil + val it = coll.iterator() + while (it.hasNext) xs = it.next() :: xs + fromIterableWithSameElemType(xs) + } +} + +/** A trait representing indexable collections with finite length */ +trait ArrayLike[+A] extends Any { + def length: Int + def apply(i: Int): A +} diff --git a/src/main/scala/strawman/collection/View.scala b/src/main/scala/strawman/collection/View.scala index e6258e7098..5b58ca66d0 100644 --- a/src/main/scala/strawman/collection/View.scala +++ b/src/main/scala/strawman/collection/View.scala @@ -1,9 +1,8 @@ package strawman.collection -import Predef.{augmentString => _, wrapString => _, _} -import scala.reflect.ClassTag -import annotation.unchecked.uncheckedVariance -import annotation.tailrec +import scala.{Int, Boolean, Nothing, annotation} +import scala.Predef.intWrapper +import strawman.collection.mutable.Iterator /** Concrete collection type: View */ trait View[+A] extends Iterable[A] with IterableLike[A, View] { @@ -117,7 +116,7 @@ trait IndexedView[+A] extends View[A] with ArrayLike[A] { self => def iterator(): Iterator[A] = new Iterator[A] { private var current = 0 def hasNext = current < self.length - def next: A = { + def next(): A = { val r = apply(current) current += 1 r diff --git a/src/main/scala/strawman/collection/immutable/LazyList.scala b/src/main/scala/strawman/collection/immutable/LazyList.scala new file mode 100644 index 0000000000..b9cf342948 --- /dev/null +++ b/src/main/scala/strawman/collection/immutable/LazyList.scala @@ -0,0 +1,56 @@ +package strawman.collection.immutable + +import scala.{Option, Some, None, Nothing, StringContext} +import strawman.collection.{IterableFactory, Iterable, LinearSeq, SeqLike} +import strawman.collection.mutable.Iterator + +class LazyList[+A](expr: => LazyList.Evaluated[A]) + extends LinearSeq[A] with SeqLike[A, LazyList] { + private[this] var evaluated = false + private[this] var result: LazyList.Evaluated[A] = _ + + def force: LazyList.Evaluated[A] = { + if (!evaluated) { + result = expr + evaluated = true + } + result + } + + 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))) + + def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) + + override def className = "LazyList" + + override def toString = + if (evaluated) + result match { + case None => "Empty" + case Some((hd, tl)) => s"$hd #:: $tl" + } + else "LazyList(?)" +} + +object LazyList extends IterableFactory[LazyList] { + + type Evaluated[+A] = Option[(A, LazyList[A])] + + object Empty extends LazyList[Nothing](None) + + object #:: { + def unapply[A](s: LazyList[A]): Evaluated[A] = s.force + } + + def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { + case coll: LazyList[B] => coll + case _ => fromIterator(coll.iterator()) + } + + def fromIterator[B](it: Iterator[B]): LazyList[B] = + new LazyList(if (it.hasNext) Some(it.next(), fromIterator(it)) else None) +} diff --git a/src/main/scala/strawman/collection/immutable/List.scala b/src/main/scala/strawman/collection/immutable/List.scala new file mode 100644 index 0000000000..623062dde8 --- /dev/null +++ b/src/main/scala/strawman/collection/immutable/List.scala @@ -0,0 +1,55 @@ +package strawman.collection.immutable + +import scala.annotation.unchecked.uncheckedVariance +import scala.Nothing +import scala.Predef.??? +import strawman.collection.{Iterable, IterableFactory, IterableOnce, LinearSeq, SeqLike} +import strawman.collection.mutable.{Buildable, ListBuffer} + + +/** Concrete collection type: List */ +sealed trait List[+A] + extends LinearSeq[A] + with SeqLike[A, List] + with Buildable[A, List[A]] { + + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + + protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList) + + /** Prepend element */ + def :: [B >: A](elem: B): List[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 + + /** When concatenating with another list `xs`, avoid copying `xs` */ + override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { + case xs: List[B] => this ++: xs + case _ => super.++(xs) + } + + override def className = "List" +} + +case class :: [+A](x: A, private[collection] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { + override def isEmpty = false + override def head = x + override def tail = next +} + +case object Nil extends List[Nothing] { + override def isEmpty = true + override def head = ??? + override def tail = ??? +} + +object List extends IterableFactory[List] { + def fromIterable[B](coll: Iterable[B]): List[B] = coll match { + case coll: List[B] => coll + case _ => ListBuffer.fromIterable(coll).toList + } +} \ No newline at end of file diff --git a/src/main/scala/strawman/collection/javaSupport.scala b/src/main/scala/strawman/collection/javaSupport.scala new file mode 100644 index 0000000000..924542f117 --- /dev/null +++ b/src/main/scala/strawman/collection/javaSupport.scala @@ -0,0 +1,112 @@ +package strawman.collection + +import strawman.collection.immutable.List + +import scala.{Array, Char, Int, AnyVal} +import scala.Predef.String +import strawman.collection.mutable.{ArrayBuffer, Buildable, StringBuilder} + +import scala.reflect.ClassTag + +class StringOps(val s: String) + extends AnyVal with IterableOps[Char] + with SeqMonoTransforms[Char, String] + with IterablePolyTransforms[Char, List] + with Buildable[Char, String] + with ArrayLike[Char] { + + protected def coll = new StringView(s) + def iterator() = coll.iterator() + + protected def fromIterableWithSameElemType(coll: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- coll) sb += ch + sb.result + } + + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + + protected[this] def newBuilder = new StringBuilder + + def length = s.length + def apply(i: Int) = s.charAt(i) + + override def knownSize = s.length + + override def className = "String" + + /** Overloaded version of `map` that gives back a string, where the inherited + * version gives back a sequence. + */ + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- s) sb += f(ch) + sb.result + } + + /** Overloaded version of `flatMap` that gives back a string, where the inherited + * version gives back a sequence. + */ + def flatMap(f: Char => String): String = { + val sb = new StringBuilder + for (ch <- s) sb ++= f(ch) + sb.result + } + + /** Overloaded version of `++` that gives back a string, where the inherited + * version gives back a sequence. + */ + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder() ++= s + for (ch <- xs.iterator()) sb += ch + sb.result + } + + /** Another overloaded version of `++`. */ + def ++(xs: String): String = s + xs +} + +case class StringView(s: String) extends IndexedView[Char] { + def length = s.length + def apply(n: Int) = s.charAt(n) + override def className = "StringView" +} + + +class ArrayOps[A](val xs: Array[A]) + extends AnyVal with IterableOps[A] + with SeqMonoTransforms[A, Array[A]] + with Buildable[A, Array[A]] + with ArrayLike[A] { + + protected def coll = new ArrayView(xs) + def iterator() = coll.iterator() + + def length = xs.length + def apply(i: Int) = xs.apply(i) + + override def view = new ArrayView(xs) + + def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) + + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] + + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) + + override def knownSize = xs.length + + override def className = "Array" + + def map[B: ClassTag](f: A => B): Array[B] = fromIterable(View.Map(coll, f)) + def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A : ClassTag](xs: IterableOnce[B]): Array[B] = fromIterable(View.Concat(coll, xs)) + def zip[B: ClassTag](xs: IterableOnce[B]): Array[(A, B)] = fromIterable(View.Zip(coll, xs)) +} + +case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { + def length = xs.length + def apply(n: Int) = xs(n) + override def className = "ArrayView" +} diff --git a/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala b/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala new file mode 100644 index 0000000000..740e71f4b8 --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala @@ -0,0 +1,93 @@ +package strawman.collection.mutable + +import scala.{Array, Int, Boolean, Unit, AnyRef} +import scala.Predef.intWrapper +import strawman.collection.{IndexedView, Iterable, IterableFactory, IterableOnce, Seq, SeqLike} + +/** Concrete collection type: ArrayBuffer */ +class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) + extends Seq[A] + with SeqLike[A, ArrayBuffer] + with Buildable[A, ArrayBuffer[A]] + with Builder[A, ArrayBuffer[A]] { + + def this() = this(new Array[AnyRef](16), 0) + + private var elems: Array[AnyRef] = initElems + private var start = 0 + private var end = initLength + + def apply(n: Int) = elems(start + n).asInstanceOf[A] + + def length = end - start + override def knownSize = length + + override def view = new ArrayBufferView(elems, start, end) + + def iterator() = view.iterator() + + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + + protected[this] def newBuilder = new ArrayBuffer[A] + + def +=(elem: A): this.type = { + if (end == elems.length) { + if (start > 0) { + Array.copy(elems, start, elems, 0, length) + end -= start + start = 0 + } + else { + val newelems = new Array[AnyRef](end * 2) + Array.copy(elems, 0, newelems, 0, end) + elems = newelems + } + } + elems(end) = elem.asInstanceOf[AnyRef] + end += 1 + this + } + + def result = this + + /** New operation: destructively drop elements at start of buffer. */ + def trimStart(n: Int): Unit = start += (n max 0) + + /** Overridden to use array copying for efficiency where possible. */ + override def ++[B >: A](xs: IterableOnce[B]): ArrayBuffer[B] = xs match { + case xs: ArrayBuffer[B] => + val elems = new Array[AnyRef](length + xs.length) + Array.copy(this.elems, this.start, elems, 0, this.length) + Array.copy(xs.elems, xs.start, elems, this.length, xs.length) + new ArrayBuffer(elems, elems.length) + case _ => super.++(xs) + } + + override def take(n: Int) = { + val elems = new Array[AnyRef](n min length) + Array.copy(this.elems, this.start, elems, 0, elems.length) + new ArrayBuffer(elems, elems.length) + } + + override def className = "ArrayBuffer" +} + +object ArrayBuffer extends IterableFactory[ArrayBuffer] { + + /** Avoid reallocation of buffer if length is known. */ + def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = + if (coll.knownSize >= 0) { + val elems = new Array[AnyRef](coll.knownSize) + val it = coll.iterator() + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + } + else new ArrayBuffer[B] ++= coll +} + +class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { + def length = end - start + def apply(n: Int) = elems(start + n).asInstanceOf[A] + override def className = "ArrayBufferView" +} diff --git a/src/main/scala/strawman/collection/mutable/Builder.scala b/src/main/scala/strawman/collection/mutable/Builder.scala new file mode 100644 index 0000000000..cbd19aa56e --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/Builder.scala @@ -0,0 +1,61 @@ +package strawman.collection.mutable + +import scala.{Boolean, Any, Char} +import java.lang.String +import strawman.collection.{IterableMonoTransforms, IterableOnce} + +/** Base trait for collection builders */ +trait Builder[-A, +To] { self => + + /** Append an element */ + def +=(x: A): this.type + + /** Result collection consisting of all elements appended so far. */ + def result: To + + /** Bulk append. Can be overridden if specialized implementations are available. */ + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator().foreach(+=) + this + } + + /** A builder resulting from this builder my mapping the result using `f`. */ + def mapResult[NewTo](f: To => NewTo) = new Builder[A, NewTo] { + def +=(x: A): this.type = { self += x; this } + override def ++=(xs: IterableOnce[A]): this.type = { self ++= xs; this } + def result: NewTo = f(self.result) + } +} + +/** Base trait for strict collections that can be built using a builder. + * @tparam A the element type of the collection + * @tparam Repr the type of the underlying collection + */ +trait Buildable[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + + /** Creates a new builder. */ + protected[this] def newBuilder: Builder[A, Repr] + + /** Optimized, push-based version of `partition`. */ + override def partition(p: A => Boolean): (Repr, Repr) = { + val l, r = newBuilder + coll.iterator().foreach(x => (if (p(x)) l else r) += x) + (l.result, r.result) + } + + // one might also override other transforms here to avoid generating + // iterators if it helps efficiency. +} + +class StringBuilder extends Builder[Char, String] { + private val sb = new java.lang.StringBuilder + + def += (x: Char) = { sb.append(x); this } + + /** Overloaded version of `++=` that takes a string */ + def ++= (s: String) = { sb.append(s); this } + + def result = sb.toString + + override def toString = result +} \ 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 new file mode 100644 index 0000000000..51ab5c7cba --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/Iterator.scala @@ -0,0 +1,111 @@ +package strawman.collection.mutable + +import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException} +import strawman.collection.{IndexedView, IterableOnce} + +/** A core Iterator class */ +trait Iterator[+A] { self => + def hasNext: Boolean + def next(): A + def foldLeft[B](z: B)(op: (B, A) => B): B = + if (hasNext) foldLeft(op(z, next()))(op) else z + def foldRight[B](z: B)(op: (A, B) => B): B = + if (hasNext) op(next(), foldRight(z)(op)) else z + def foreach(f: A => Unit): Unit = + while (hasNext) f(next()) + def indexWhere(p: A => Boolean): Int = { + var i = 0 + while (hasNext) { + if (p(next())) return i + i += 1 + } + -1 + } + def length = { + var len = 0 + while (hasNext) { len += 1; next() } + len + } + def filter(p: A => Boolean): Iterator[A] = new Iterator[A] { + private var hd: A = _ + private var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + do { + if (!self.hasNext) return false + hd = self.next() + } while (!p(hd)) + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + def map[B](f: A => B): Iterator[B] = new Iterator[B] { + def hasNext = self.hasNext + def next() = f(self.next()) + } + + def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = Iterator.empty + private def current = { + while (!myCurrent.hasNext && self.hasNext) + myCurrent = f(self.next()).iterator() + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def ++[B >: A](xs: IterableOnce[B]): Iterator[B] = new Iterator[B] { + private var myCurrent: Iterator[B] = self + private var first = true + private def current = { + if (!myCurrent.hasNext && first) { + myCurrent = xs.iterator() + first = false + } + myCurrent + } + def hasNext = current.hasNext + def next() = current.next() + } + def take(n: Int): Iterator[A] = new Iterator[A] { + private var i = 0 + def hasNext = self.hasNext && i < n + def next() = + if (hasNext) { + i += 1 + self.next() + } + else Iterator.empty.next() + } + def drop(n: Int): Iterator[A] = { + var i = 0 + while (i < n && hasNext) { + next() + i += 1 + } + this + } + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new Iterator[(A, B)] { + val thatIterator = that.iterator() + def hasNext = self.hasNext && thatIterator.hasNext + def next() = (self.next(), thatIterator.next()) + } +} + +object Iterator { + val empty: Iterator[Nothing] = new Iterator[Nothing] { + def hasNext = false + def next() = throw new NoSuchElementException("next on empty iterator") + } + def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { + val length = xs.length + def apply(n: Int) = xs(n) + }.iterator() +} diff --git a/src/main/scala/strawman/collection/mutable/ListBuffer.scala b/src/main/scala/strawman/collection/mutable/ListBuffer.scala new file mode 100644 index 0000000000..bf7fa1eca6 --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/ListBuffer.scala @@ -0,0 +1,61 @@ +package strawman.collection.mutable + +import scala.{Int, Unit} +import strawman.collection.{SeqLike, IterableFactory, Iterable, Seq} +import strawman.collection.immutable.{List, Nil, ::} + +/** Concrete collection type: ListBuffer */ +class ListBuffer[A] + extends Seq[A] + with SeqLike[A, ListBuffer] + with Buildable[A, ListBuffer[A]] + with Builder[A, ListBuffer[A]] { + + private var first, last: List[A] = Nil + private var aliased = false + private var len = 0 + + def iterator() = first.iterator() + + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + + def apply(i: Int) = first.apply(i) + + def length = len + override def knownSize = len + + protected[this] def newBuilder = new ListBuffer[A] + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + + /** Convert to list; avoids copying where possible. */ + def toList = { + aliased = true + first + } + + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = elem :: Nil + last match { + case last: ::[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + len += 1 + this + } + + def result = this + + override def className = "ListBuffer" +} + +object ListBuffer extends IterableFactory[ListBuffer] { + def fromIterable[B](coll: Iterable[B]): ListBuffer[B] = new ListBuffer[B] ++= coll +} diff --git a/src/main/scala/strawman/collection/package.scala b/src/main/scala/strawman/collection/package.scala index a94d813e5f..ad5664fc5e 100644 --- a/src/main/scala/strawman/collection/package.scala +++ b/src/main/scala/strawman/collection/package.scala @@ -1,7 +1,10 @@ package strawman -import Predef.{augmentString => _, wrapString => _, _} +import scala.{AnyVal, Array, Char, Int, Unit} +import scala.Predef.String import scala.reflect.ClassTag +import strawman.collection.immutable.List +import strawman.collection.mutable.{ArrayBuffer, Buildable} /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose @@ -81,104 +84,18 @@ import scala.reflect.ClassTag * map, flatMap, ++, zip */ package object collection extends LowPriority { + import scala.language.implicitConversions // ------------------ Decorators to add collection ops to existing types ----------------------- - /** Decorator to add collection operations to strings. - */ - implicit class StringOps(val s: String) - extends AnyVal with IterableOps[Char] - with SeqMonoTransforms[Char, String] - with IterablePolyTransforms[Char, List] - with Buildable[Char, String] - with ArrayLike[Char] { + /** Decorator to add collection operations to strings. */ + implicit def stringToStringOps(s: String): StringOps = new StringOps(s) - protected def coll = new StringView(s) - def iterator() = coll.iterator() - - protected def fromIterableWithSameElemType(coll: Iterable[Char]): String = { - val sb = new StringBuilder - for (ch <- coll) sb += ch - sb.result - } - - def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) - - protected[this] def newBuilder = new StringBuilder - - def length = s.length - def apply(i: Int) = s.charAt(i) - - override def knownSize = s.length - - override def className = "String" - - /** Overloaded version of `map` that gives back a string, where the inherited - * version gives back a sequence. - */ - def map(f: Char => Char): String = { - val sb = new StringBuilder - for (ch <- s) sb += f(ch) - sb.result - } - - /** Overloaded version of `flatMap` that gives back a string, where the inherited - * version gives back a sequence. - */ - def flatMap(f: Char => String): String = { - val sb = new StringBuilder - for (ch <- s) sb ++= f(ch) - sb.result - } - - /** Overloaded version of `++` that gives back a string, where the inherited - * version gives back a sequence. - */ - def ++(xs: IterableOnce[Char]): String = { - val sb = new StringBuilder() ++= s - for (ch <- xs.iterator()) sb += ch - sb.result - } - - /** Another overloaded version of `++`. */ - def ++(xs: String): String = s + xs - } - - /** Decorator to add collection operations to arrays. - */ - implicit class ArrayOps[A](val xs: Array[A]) - extends AnyVal with IterableOps[A] - with SeqMonoTransforms[A, Array[A]] - with Buildable[A, Array[A]] - with ArrayLike[A] { - - protected def coll = new ArrayView(xs) - def iterator() = coll.iterator() - - def length = xs.length - def apply(i: Int) = xs.apply(i) - - override def view = new ArrayView(xs) - - def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) - - protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) - - def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] - - protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) - - override def knownSize = xs.length - - override def className = "Array" - - def map[B: ClassTag](f: A => B): Array[B] = fromIterable(View.Map(coll, f)) - def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = fromIterable(View.FlatMap(coll, f)) - def ++[B >: A : ClassTag](xs: IterableOnce[B]): Array[B] = fromIterable(View.Concat(coll, xs)) - def zip[B: ClassTag](xs: IterableOnce[B]): Array[(A, B)] = fromIterable(View.Zip(coll, xs)) - } + /** Decorator to add collection operations to arrays. */ + implicit def arrayToArrayOps[A](as: Array[A]): ArrayOps[A] = new ArrayOps[A](as) } class LowPriority { + import scala.language.implicitConversions import strawman.collection._ /** Convert array to iterable via view. Lower priority than ArrayOps */ diff --git a/src/test/scala/strawman/collection/test/Test.scala b/src/test/scala/strawman/collection/test/Test.scala index 3c62d46113..5b7922a982 100644 --- a/src/test/scala/strawman/collection/test/Test.scala +++ b/src/test/scala/strawman/collection/test/Test.scala @@ -1,7 +1,12 @@ package strawman.collection.test -import scala.Predef.{augmentString => _, booleanArrayOps => _, genericArrayOps => _, genericWrapArray => _, intArrayOps => _, refArrayOps => _, wrapBooleanArray => _, wrapIntArray => _, wrapRefArray => _, wrapString => _, _} +import java.lang.String +import scala.{Int, Unit, Array, StringContext, Boolean, Any, Char} +import scala.Predef.{println, charWrapper} + import strawman.collection._ +import strawman.collection.immutable._ +import strawman.collection.mutable._ import org.junit.Test class StrawmanTest { @@ -175,13 +180,13 @@ class StrawmanTest { val ys7: Array[Int] = xs7 val xs8 = xs.drop(2) val ys8: Array[Int] = xs8 - val xs9 = ArrayOps(xs).map(_ >= 0) + val xs9 = arrayToArrayOps(xs).map(_ >= 0) val ys9: Array[Boolean] = xs9 val xs10 = xs.flatMap(x => List(x, -x)) val ys10: Array[Int] = xs10 val xs11 = xs ++ xs val ys11: Array[Int] = xs11 - val xs12 = ArrayOps(xs) ++ Nil + val xs12 = arrayToArrayOps(xs) ++ Nil val ys12: Array[Int] = xs12 val xs13 = Nil ++ xs val ys13: List[Int] = xs13