From 0c489f549de620424e5cb65c082a28568cade389 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 25 Jul 2016 19:19:18 +0200 Subject: [PATCH 01/17] Add arrays to collection strawman This PR investigates what it takes to extend CollectionStrawMan5 to arrays. --- .../collections/CollectionStrawMan6.scala | 584 ++++++++++++++++++ tests/run/colltest6.check | 102 +++ .../run/colltest6/CollectionStrawMan6_1.scala | 584 ++++++++++++++++++ tests/run/colltest6/CollectionTests_2.scala | 226 +++++++ 4 files changed, 1496 insertions(+) create mode 100644 src/strawman/collections/CollectionStrawMan6.scala create mode 100644 tests/run/colltest6.check create mode 100644 tests/run/colltest6/CollectionStrawMan6_1.scala create mode 100644 tests/run/colltest6/CollectionTests_2.scala diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala new file mode 100644 index 000000000000..2596d2a4a4b1 --- /dev/null +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -0,0 +1,584 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance +import annotation.tailrec + +class LowPriority { + import CollectionStrawMan6._ + + implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = + new ArrayView[T](xs) + +} + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to odether + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + * + * Strawman6 is like strawman5, and adds arrays and some utilitity methods + * (tail, mkString). TODO: Generalize Builders. + * + */ +object CollectionStrawMan6 extends LowPriority { + + /* ------------ 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] { + protected def coll: Iterable[A] = this + def knownLength: Int = -1 + def size: Int = + if (knownLength >= 0) knownLength + else foldLeft(0)((len, x) => len + 1) + def className = getClass.getName + override def toString = this.mkString(className ++ "(", ", ", ")") + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { + def apply(i: Int): A + def length: Int + } + + /** Base trait for strict collections */ + trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { + protected[this] def newBuilder: Builder[A, To] + override def partition(p: A => Boolean): (To, To) = { + val l, r = newBuilder + 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] { + def +=(x: A): this.type + def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } + } + + /* ------------ 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] { + protected[this] def fromLikeIterable(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 + + trait IterableOps[+A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + def view: View[A] = View.fromIterator(iterator) + def mkString(lead: String, sep: String, follow: String): String = { + var first: Boolean = true + val b = new StringBuilder(lead) + foreach { elem => + if (!first) b.append(sep) + first = false + b.append(elem) + } + b.append(follow).toString + } + } + + trait IterableMonoTransforms[+A, +Repr] extends Any { + protected def coll: Iterable[A] + protected[this] def fromLikeIterable(coll: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + } + def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) + def tail: Repr = drop(1) + 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) + } + + trait IterablePolyTransforms[+A, +C[A]] extends Any { + protected def coll: Iterable[A] + def fromIterable[B](coll: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote + } + + trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + def reverse: Repr = { + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def length: Int = + if (isEmpty) 0 else 1 + tail.length + protected[this] def newBuilder = new ListBuffer[A] + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) + override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { + case xs: List[B] => this ++: xs + case _ => super.++(xs) + } + @tailrec final override def drop(n: Int) = + if (n > 0) tail.drop(n - 1) else this + override def className = "List" + } + + case class Cons[+A](x: A, private[collections] 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).result + } + } + + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + 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 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 knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + 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 + def trimStart(n: Int): Unit = start += (n max 0) + 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 className = "ArrayBuffer" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = + if (coll.knownLength >= 0) { + val elems = new Array[AnyRef](coll.knownLength) + val it = coll.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + } + else { + val buf = new ArrayBuffer[B] + val it = coll.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + override def className = "ArrayBufferView" + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) + extends AnyVal with IterableOps[Char] + with SeqMonoTransforms[Char, String] + with IterablePolyTransforms[Char, List] { + protected def coll = new StringView(s) + def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- coll) sb.append(ch) + sb.toString + } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + override def className = "StringView" + } + + implicit class ArrayOps[A](val xs: Array[A]) + extends AnyVal with IterableOps[A] + with SeqMonoTransforms[A, Array[A]] { + + protected def coll = new ArrayView(xs) + def iterator = coll.iterator + + private def fill[B](xs: Array[B], coll: Iterable[B]): Array[B] = { + val it = coll.iterator + var i = 0 + while (it.hasNext) { + xs(i) = it.next + i += 1 + } + xs + } + + override def view = new ArrayView(xs) + + protected def fromLikeIterable(coll: Iterable[A]): Array[A] = + fill( + java.lang.reflect.Array.newInstance(xs.getClass.getComponentType, coll.size) + .asInstanceOf[Array[A]], + coll) + + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = + fill(new Array[B](coll.size), coll) + + def fromIterable1[B: ClassTag](coll: Iterable[B]): Array[B] = + fill(new Array[B](coll.size), coll) + + def map[B: ClassTag](f: A => B): Array[B] = fromIterable1(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 RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + override def className = "ArrayView" + } + +/* ---------- Views -------------------------------------------------------*/ + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A, View] { + override def view = this + override def fromIterable[B](c: Iterable[B]): View[B] = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + override def className = "View" + } + + /** View defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left = Partitioned(this, true) + val right = Partitioned(this, false) + } + case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { + def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + 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 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 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 RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} diff --git a/tests/run/colltest6.check b/tests/run/colltest6.check new file mode 100644 index 000000000000..39acfa426345 --- /dev/null +++ b/tests/run/colltest6.check @@ -0,0 +1,102 @@ +------- +123 +123 +1 +1 +List(1, 2, 3) +List(2) +List(1, 3) +List(3) +List(true, true, true) +List(1, -1, 2, -2, 3, -3) +List(1, 2, 3, 1, 2, 3) +List(1, 2, 3) +List(1, 2, 3) +List(1, 2, 3, a) +List((1,true), (2,true), (3,true)) +List(3, 2, 1) +------- +123 +123 +1 +1 +List(1, 2, 3) +ArrayBuffer(2) +ArrayBuffer(1, 3) +ArrayBuffer(3) +ArrayBuffer(true, true, true) +ArrayBuffer(1, -1, 2, -2, 3, -3) +ArrayBuffer(1, 2, 3, 1, 2, 3) +ArrayBuffer(1, 2, 3) +List(1, 2, 3) +ArrayBuffer(1, 2, 3, a) +ArrayBuffer((1,true), (2,true), (3,true)) +ArrayBuffer(3, 2, 1) +------- +123 +123 +1 +1 +List(1, 2, 3) +ListBuffer(2) +ListBuffer(1, 3) +ListBuffer(3) +ListBuffer(true, true, true) +ListBuffer(1, -1, 2, -2, 3, -3) +ListBuffer(1, 2, 3, 1, 2, 3) +ListBuffer(1, 2, 3) +List(1, 2, 3) +ListBuffer(1, 2, 3, a) +ListBuffer((1,true), (2,true), (3,true)) +ListBuffer(3, 2, 1) +------- +123 +123 +1 +1 +List(1, 2, 3) +List(2) +List(1, 3) +List(3) +List(true, true, true) +List(1, -1, 2, -2, 3, -3) +List(1, 2, 3, 1, 2, 3) +List(1, 2, 3) +List(1, 2, 3) +List(1, 2, 3, a) +List((1,true), (2,true), (3,true)) +------- +abc +abc +1 +a +List(a, b, c) +b +ac +c +List(98, 99, 100) +ABC +a,ab,bc,c +abcabc +abcxy +abc +List(a, b, c) +List(a, b, c, xyz) +List((a,98), (b,99), (c,100)) +------- +123 +123 +1 +1 +List(1, 2, 3) +ArrayView(2) +ArrayView(1, 3) +ArrayView(3) +ArrayView(true, true, true) +ArrayView(1, -1, 2, -2, 3, -3) +ArrayView(1, 2, 3, 1, 2, 3) +ArrayView(1, 2, 3) +List(1, 2, 3) +ArrayView(1, 2, 3, a) +ArrayView((1,true), (2,true), (3,true)) +ArrayView(3, 2, 1) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala new file mode 100644 index 000000000000..2596d2a4a4b1 --- /dev/null +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -0,0 +1,584 @@ +package strawman.collections + +import Predef.{augmentString => _, wrapString => _, _} +import scala.reflect.ClassTag +import annotation.unchecked.uncheckedVariance +import annotation.tailrec + +class LowPriority { + import CollectionStrawMan6._ + + implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = + new ArrayView[T](xs) + +} + +/** A strawman architecture for new collections. It contains some + * example collection classes and methods with the intent to expose + * some key issues. It would be good to compare this to odether + * implementations of the same functionality, to get an idea of the + * strengths and weaknesses of different collection architectures. + * + * For a test file, see tests/run/CollectionTests.scala. + * + * Strawman6 is like strawman5, and adds arrays and some utilitity methods + * (tail, mkString). TODO: Generalize Builders. + * + */ +object CollectionStrawMan6 extends LowPriority { + + /* ------------ 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] { + protected def coll: Iterable[A] = this + def knownLength: Int = -1 + def size: Int = + if (knownLength >= 0) knownLength + else foldLeft(0)((len, x) => len + 1) + def className = getClass.getName + override def toString = this.mkString(className ++ "(", ", ", ")") + } + + /** Base trait for sequence collections */ + trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { + def apply(i: Int): A + def length: Int + } + + /** Base trait for strict collections */ + trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { + protected[this] def newBuilder: Builder[A, To] + override def partition(p: A => Boolean): (To, To) = { + val l, r = newBuilder + 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] { + def +=(x: A): this.type + def result: To + + def ++=(xs: IterableOnce[A]): this.type = { + xs.iterator.foreach(+=) + this + } + } + + /* ------------ 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] { + protected[this] def fromLikeIterable(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 + + trait IterableOps[+A] extends Any { + def iterator: Iterator[A] + def foreach(f: A => Unit): Unit = iterator.foreach(f) + def foldLeft[B](z: B)(op: (B, A) => B): B = iterator.foldLeft(z)(op) + def foldRight[B](z: B)(op: (A, B) => B): B = iterator.foldRight(z)(op) + def indexWhere(p: A => Boolean): Int = iterator.indexWhere(p) + def isEmpty: Boolean = !iterator.hasNext + def head: A = iterator.next + def view: View[A] = View.fromIterator(iterator) + def mkString(lead: String, sep: String, follow: String): String = { + var first: Boolean = true + val b = new StringBuilder(lead) + foreach { elem => + if (!first) b.append(sep) + first = false + b.append(elem) + } + b.append(follow).toString + } + } + + trait IterableMonoTransforms[+A, +Repr] extends Any { + protected def coll: Iterable[A] + protected[this] def fromLikeIterable(coll: Iterable[A]): Repr + def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, p)) + def partition(p: A => Boolean): (Repr, Repr) = { + val pn = View.Partition(coll, p) + (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + } + def drop(n: Int): Repr = fromLikeIterable(View.Drop(coll, n)) + def tail: Repr = drop(1) + 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) + } + + trait IterablePolyTransforms[+A, +C[A]] extends Any { + protected def coll: Iterable[A] + def fromIterable[B](coll: Iterable[B]): C[B] + def map[B](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + def flatMap[B](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + def ++[B >: A](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote + } + + trait SeqMonoTransforms[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { + def reverse: Repr = { + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next, xs) + fromLikeIterable(xs) + } + } + + /* --------- Concrete collection types ------------------------------- */ + + /** Concrete collection type: List */ + sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean + def head: A + def tail: List[A] + def iterator = new Iterator[A] { + private[this] var current = self + def hasNext = !current.isEmpty + def next = { val r = current.head; current = current.tail; r } + } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + def length: Int = + if (isEmpty) 0 else 1 + tail.length + protected[this] def newBuilder = new ListBuffer[A] + def ++:[B >: A](prefix: List[B]): List[B] = + if (prefix.isEmpty) this + else Cons(prefix.head, prefix.tail ++: this) + override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match { + case xs: List[B] => this ++: xs + case _ => super.++(xs) + } + @tailrec final override def drop(n: Int) = + if (n > 0) tail.drop(n - 1) else this + override def className = "List" + } + + case class Cons[+A](x: A, private[collections] 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).result + } + } + + /** Concrete collection type: ListBuffer */ + class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + private var first, last: List[A] = Nil + private var aliased = false + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + + private def copyElems(): Unit = { + val buf = ListBuffer.fromIterable(result) + first = buf.first + last = buf.last + aliased = false + } + def result = { + aliased = true + first + } + def +=(elem: A) = { + if (aliased) copyElems() + val last1 = Cons(elem, Nil) + last match { + case last: Cons[A] => last.next = last1 + case _ => first = last1 + } + last = last1 + 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 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 knownLength = length + override def view = new ArrayBufferView(elems, start, end) + def iterator = view.iterator + def fromIterable[B](it: Iterable[B]): ArrayBuffer[B] = + ArrayBuffer.fromIterable(it) + 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 + def trimStart(n: Int): Unit = start += (n max 0) + 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 className = "ArrayBuffer" + } + + object ArrayBuffer extends IterableFactory[ArrayBuffer] { + def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = + if (coll.knownLength >= 0) { + val elems = new Array[AnyRef](coll.knownLength) + val it = coll.iterator + for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](elems, elems.length) + } + else { + val buf = new ArrayBuffer[B] + val it = coll.iterator + while (it.hasNext) buf += it.next() + buf + } + } + + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + def apply(n: Int) = elems(start + n).asInstanceOf[A] + override def className = "ArrayBufferView" + } + + /** Concrete collection type: String */ + implicit class StringOps(val s: String) + extends AnyVal with IterableOps[Char] + with SeqMonoTransforms[Char, String] + with IterablePolyTransforms[Char, List] { + protected def coll = new StringView(s) + def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { + val sb = new StringBuilder + for (ch <- coll) sb.append(ch) + sb.toString + } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + def map(f: Char => Char): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def flatMap(f: Char => String): String = { + val sb = new StringBuilder + for (ch <- s) sb.append(f(ch)) + sb.toString + } + def ++(xs: IterableOnce[Char]): String = { + val sb = new StringBuilder(s) + for (ch <- xs.iterator) sb.append(ch) + sb.toString + } + def ++(xs: String): String = s + xs + } + + case class StringView(s: String) extends RandomAccessView[Char] { + val start = 0 + val end = s.length + def apply(n: Int) = s.charAt(n) + override def className = "StringView" + } + + implicit class ArrayOps[A](val xs: Array[A]) + extends AnyVal with IterableOps[A] + with SeqMonoTransforms[A, Array[A]] { + + protected def coll = new ArrayView(xs) + def iterator = coll.iterator + + private def fill[B](xs: Array[B], coll: Iterable[B]): Array[B] = { + val it = coll.iterator + var i = 0 + while (it.hasNext) { + xs(i) = it.next + i += 1 + } + xs + } + + override def view = new ArrayView(xs) + + protected def fromLikeIterable(coll: Iterable[A]): Array[A] = + fill( + java.lang.reflect.Array.newInstance(xs.getClass.getComponentType, coll.size) + .asInstanceOf[Array[A]], + coll) + + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = + fill(new Array[B](coll.size), coll) + + def fromIterable1[B: ClassTag](coll: Iterable[B]): Array[B] = + fill(new Array[B](coll.size), coll) + + def map[B: ClassTag](f: A => B): Array[B] = fromIterable1(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 RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + override def className = "ArrayView" + } + +/* ---------- Views -------------------------------------------------------*/ + + /** Concrete collection type: View */ + trait View[+A] extends Iterable[A] with IterableLike[A, View] { + override def view = this + override def fromIterable[B](c: Iterable[B]): View[B] = c match { + case c: View[B] => c + case _ => View.fromIterator(c.iterator) + } + override def className = "View" + } + + /** View defined in terms of indexing a range */ + trait RandomAccessView[+A] extends View[A] { + def start: Int + def end: Int + def apply(i: Int): A + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + override def knownLength = end - start max 0 + } + + object View { + def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { + def iterator = it + } + case object Empty extends View[Nothing] { + def iterator = Iterator.empty + override def knownLength = 0 + } + case class Elems[A](xs: A*) extends View[A] { + def iterator = Iterator(xs: _*) + override def knownLength = xs.length + } + case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { + def iterator = underlying.iterator.filter(p) + } + case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + val left = Partitioned(this, true) + val right = Partitioned(this, false) + } + case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { + def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) + } + case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.drop(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + } + case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { + def iterator = underlying.iterator.map(f) + override def knownLength = underlying.knownLength + } + case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { + def iterator = underlying.iterator.flatMap(f) + } + case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { + def iterator = underlying.iterator ++ other + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength + other.knownLength + case _ => + -1 + } + } + case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { + def iterator = underlying.iterator.zip(other) + override def knownLength = other match { + case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => + underlying.knownLength min other.knownLength + case _ => + -1 + } + } + } + +/* ---------- Iterators ---------------------------------------------------*/ + + /** A core Iterator class */ + trait Iterator[+A] extends IterableOnce[A] { self => + def hasNext: Boolean + def next(): A + def iterator = this + 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 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 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 RandomAccessView[A] { + val start = 0 + val end = xs.length + def apply(n: Int) = xs(n) + }.iterator + } +} diff --git a/tests/run/colltest6/CollectionTests_2.scala b/tests/run/colltest6/CollectionTests_2.scala new file mode 100644 index 000000000000..659bfb9536a3 --- /dev/null +++ b/tests/run/colltest6/CollectionTests_2.scala @@ -0,0 +1,226 @@ +import Predef.{augmentString => _, wrapString => _, intArrayOps => _, booleanArrayOps => _, genericArrayOps => _, refArrayOps => _, wrapIntArray => _, wrapBooleanArray => _, wrapRefArray => _, genericWrapArray => _, _} +import scala.reflect.ClassTag + +object Test { + import strawman.collections._ + import CollectionStrawMan6._ + + def seqOps(xs: Seq[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + 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) + val ys6: Seq[Int] = xs6 + val ys7: Seq[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Seq[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: Seq[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: Seq[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Seq[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: Seq[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: Seq[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Seq[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs10) + println(xs11) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + println(xs16) + } + + def viewOps(xs: View[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + 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) + val ys6: View[Int] = xs6 + val ys7: View[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: View[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: View[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: View[Int] = xs10 + val xs11 = xs ++ xs + val ys11: View[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: View[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: View[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: View[(Int, Boolean)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6.to(List)) + println(xs7.to(List)) + println(xs8.to(List)) + println(xs9.to(List)) + println(xs10.to(List)) + println(xs11.to(List)) + println(xs12.to(List)) + println(xs13.to(List)) + println(xs14.to(List)) + println(xs15.to(List)) + } + + def stringOps(xs: String) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + 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) + val ys6: String = xs6 + val ys7: String = xs7 + val xs8 = xs.drop(2) + val ys8: String = xs8 + val xs9 = xs.map(_ + 1) // !!! need a language change to make this work without the : Char + val ys9: Seq[Int] = xs9 + val xs9a = xs.map(_.toUpper) // !!! need a language change to make this work without the : Char + val ys9a: String = xs9a + val xs10 = xs.flatMap((x: Char) => s"$x,$x") + val ys10: String = xs10 + val xs11 = xs ++ xs + val ys11: String = xs11 + val xs11a = xs ++ List('x', 'y') // Cons('x', Cons('y', Nil)) + val ys11a: String = xs11a + val xs12 = xs ++ Nil + val ys12: String = xs12 + val xs13 = Nil ++ xs.iterator + val ys13: List[Char] = xs13 + val xs14 = xs ++ Cons("xyz", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Char, Int)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs9a) + println(xs10) + println(xs11) + println(xs11a) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + } + + + def arrayOps(xs: Array[Int]) = { + import CollectionStrawMan6.ArrayOps + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + 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) + val ys6: Array[Int] = xs6 + val ys7: Array[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Array[Int] = xs8 + val xs9 = ArrayOps(xs).map(_ >= 0) + val ys9: Array[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: Array[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Array[Int] = xs11 + val xs12 = ArrayOps(xs) ++ Nil + val ys12: Array[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ Cons("a": Any, Nil) + val ys14: Array[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Array[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Array[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6.view) + println(xs7.view) + println(xs8.view) + println(xs9.view) + println(xs10.view) + println(xs11.view) + println(xs12.view) + println(xs13) + println(xs14.view) + println(xs15.view) + println(xs16.view) + } + + def main(args: Array[String]) = { + val ints = Cons(1, Cons(2, Cons(3, Nil))) + val intsBuf = ints.to(ArrayBuffer) + val intsListBuf = ints.to(ListBuffer) + val intsView = ints.view + seqOps(ints) + seqOps(intsBuf) + seqOps(intsListBuf) + viewOps(intsView) + stringOps("abc") + arrayOps(Array(1, 2, 3)) + } +} From 84466af8e3834e64bf350fe03976ee2176bb6916 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 26 Jul 2016 23:51:04 +0200 Subject: [PATCH 02/17] Further extension with LazyList Demonstrates how to integrate lazy non-view collections in the framework. --- .../collections/CollectionStrawMan6.scala | 591 +++++++++++++++--- tests/run/colltest6.check | 32 + .../run/colltest6/CollectionStrawMan6_1.scala | 591 +++++++++++++++--- tests/run/colltest6/CollectionTests_2.scala | 76 ++- 4 files changed, 1081 insertions(+), 209 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index 2596d2a4a4b1..b5776433ee4c 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -8,9 +8,11 @@ import annotation.tailrec class LowPriority { import CollectionStrawMan6._ - implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = - new ArrayView[T](xs) + /** Convert array to iterable via view. Lower priority than ArrayOps */ + implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = new ArrayView[T](xs) + /** Convert string to iterable via view. Lower priority than StringOps */ + implicit def stringToView(s: String): StringView = new StringView(s) } /** A strawman architecture for new collections. It contains some @@ -21,9 +23,74 @@ class LowPriority { * * For a test file, see tests/run/CollectionTests.scala. * - * Strawman6 is like strawman5, and adds arrays and some utilitity methods - * (tail, mkString). TODO: Generalize Builders. + * Strawman6 is like strawman5, and adds lazy lists (i.e. lazie streams), arrays + * and some utilitity methods (take, tail, mkString, toArray). Also, systematically + * uses builders for all strict collections. * + * Types covered in this strawman: + * + * 1. Collection base types: + * + * IterableOnce, Iterable, Seq, LinearSeq, View, IndexedView + * + * 2. Collection creator base types: + * + * FromIterable, IterableFactory, Buildable, Builder + * + * 3. Types that bundle operations: + * + * IterableOps, IterableMonoTransforms, IterablePolyTransforms, IterableLike + * SeqMonoTransforms, SeqLike + * + * 4. Concrete collection types: + * + * List, LazyList, ListBuffer, ArrayBuffer, ArrayBufferView, StringView, ArrayView + * + * 5. Decorators for existing types + * + * StringOps, ArrayOps + * + * 6. Related non collection types: + * + * Iterator, StringBuilder + * + * Operations covered in this strawman: + * + * 1. Abstract operations, or expected to be overridden: + * + * For iterables: + * + * iterator, fromIterable, fromLikeIterable, knownLength, className + * + * For sequences: + * + * apply, length + * + * For buildables: + * + * newBuilder + * + * For builders: + * + * +=, result + * + * 2. Utility methods, might be overridden for performance: + * + * Operations returning not necessarily a collection: + * + * foreach, foldLeft, foldRight, indexWhere, isEmpty, head, size, mkString + * + * Operations returning a collection of a fixed type constructor: + * + * view, to, toArray, copyToArray + * + * Type-preserving generic transforms: + * + * filter, partition, take, drop, tail, reverse + * + * Generic transforms returning collections of different element types: + * + * map, flatMap, ++, zip */ object CollectionStrawMan6 extends LowPriority { @@ -47,13 +114,21 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { + + /** The collection itself */ protected def coll: Iterable[A] = this + + /** The length of this collection, if it can be cheaply computed, -1 otherwise. */ def knownLength: Int = -1 - def size: Int = - if (knownLength >= 0) knownLength - else foldLeft(0)((len, x) => len + 1) + + /** 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 - override def toString = this.mkString(className ++ "(", ", ", ")") + + override def toString = s"$className(${mkString(", ")})" } /** Base trait for sequence collections */ @@ -62,27 +137,75 @@ object CollectionStrawMan6 extends LowPriority { def length: Int } - /** Base trait for strict collections */ - trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { - protected[this] def newBuilder: Builder[A, To] - override def partition(p: A => Boolean): (To, To) = { + /** Base trait for linearly accessed sequences */ + trait LinearSeq[+A] extends Seq[A] { self => + + 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 } + } + + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + + def length: Int = if (isEmpty) 0 else 1 + tail.length + + /** Optimized version of `drop` that avoids copying */ + final override def drop(n: Int) = { + var current: Seq[A] = this + var i = n + while (i > 0) { + current = current.tail + i -= 1 + } + current + } + } + + /** Base trait for strict collections that can be built using a builder. + * @param A the element type of the collection + * @param 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 - iterator.foreach(x => (if (p(x)) l else r) += x) + 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] { + 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 ----------------------------------- */ @@ -103,98 +226,181 @@ object CollectionStrawMan6 extends LowPriority { 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. + */ protected[this] def fromLikeIterable(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 + extends IterableLike[A, C] + with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + /** Operations over iterables. No operation defined here is generic in the + * type of the underlying collection. + */ trait IterableOps[+A] extends Any { - def iterator: Iterator[A] + 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 - def head: A = iterator.next + + /** The first element of the collection. */ + def head: A = iterator.next() + + /** The number of elements in this collection. */ + def size: Int = + if (coll.knownLength >= 0) coll.knownLength else foldLeft(0)((len, x) => len + 1) + + /** A view representing the elements of this collection. */ def view: View[A] = View.fromIterator(iterator) - def mkString(lead: String, sep: String, follow: String): String = { + + /** 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(lead) + val b = new StringBuilder() foreach { elem => - if (!first) b.append(sep) + if (!first) b ++= sep first = false - b.append(elem) + b ++= String.valueOf(elem) } - b.append(follow).toString + b.result + } + + /** 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 (coll.knownLength >= 0) copyToArray(new Array[B](coll.knownLength), 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 } } + /** 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 fromLikeIterable(coll: Iterable[A]): Repr + + /** All elements satisfying predicate `p` */ def filter(p: A => Boolean): Repr = fromLikeIterable(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) (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) } + + /** A collection containing the first `n` elements of this collection. */ + def take(n: Int): Repr = fromLikeIterable(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 = fromLikeIterable(View.Drop(coll, n)) + + /** The rest of the collection without its first element. */ def tail: Repr = drop(1) - 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) } + /** 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 = { - var xs: List[A] = Nil - var it = coll.iterator - while (it.hasNext) xs = new Cons(it.next, xs) - fromLikeIterable(xs) + def reverse: Repr = coll.view match { + case v: IndexedView[A] => fromLikeIterable(v.reverse) + case _ => + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next(), xs) + fromLikeIterable(xs) } } /* --------- Concrete collection types ------------------------------- */ /** Concrete collection type: List */ - sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + sealed trait List[+A] extends LinearSeq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean def head: A def tail: List[A] - def iterator = new Iterator[A] { - private[this] var current = self - def hasNext = !current.isEmpty - def next = { val r = current.head; current = current.tail; r } - } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) - def apply(i: Int): A = { - require(!isEmpty) - if (i == 0) head else tail.apply(i - 1) - } - def length: Int = - if (isEmpty) 0 else 1 + tail.length - protected[this] def newBuilder = new ListBuffer[A] + + protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList) + + /** Prepend operation that avoids copying this list */ def ++:[B >: A](prefix: List[B]): List[B] = if (prefix.isEmpty) this else Cons(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) } - @tailrec final override def drop(n: Int) = - if (n > 0) tail.drop(n - 1) else this + override def className = "List" } @@ -214,29 +420,43 @@ object CollectionStrawMan6 extends LowPriority { object List extends IterableFactory[List] { def fromIterable[B](coll: Iterable[B]): List[B] = coll match { case coll: List[B] => coll - case _ => ListBuffer.fromIterable(coll).result + case _ => ListBuffer.fromIterable(coll).toList } } /** Concrete collection type: ListBuffer */ - class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + 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 + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + protected[this] def newBuilder = new ListBuffer[A] + private def copyElems(): Unit = { val buf = ListBuffer.fromIterable(result) first = buf.first last = buf.last aliased = false } - def result = { + + /** Convert to list; avoids copying where possible. */ + def toList = { aliased = true first } + def +=(elem: A) = { if (aliased) copyElems() val last1 = Cons(elem, Nil) @@ -247,6 +467,9 @@ object CollectionStrawMan6 extends LowPriority { last = last1 this } + + def result = this + override def className = "ListBuffer" } @@ -256,18 +479,31 @@ object CollectionStrawMan6 extends LowPriority { /** Concrete collection type: ArrayBuffer */ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) - extends Seq[A] with SeqLike[A, ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + 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 knownLength = 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) { @@ -285,8 +521,13 @@ object CollectionStrawMan6 extends LowPriority { 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) @@ -296,10 +537,18 @@ object CollectionStrawMan6 extends LowPriority { 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.knownLength >= 0) { val elems = new Array[AnyRef](coll.knownLength) @@ -315,98 +564,173 @@ object CollectionStrawMan6 extends LowPriority { } } - class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { def apply(n: Int) = elems(start + n).asInstanceOf[A] override def className = "ArrayBufferView" } - /** Concrete collection type: String */ + class LazyList[+A](expr: () => LazyList.Evaluated[A]) + extends LinearSeq[A] with SeqLike[A, LazyList] { self => + 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, self))) + + 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 "?" + } + + object LazyList extends IterableFactory[LazyList] { + + type Evaluated[+A] = Option[(A, LazyList[A])] + + def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { + case coll: LazyList[B] => coll + case _ => new LazyList(() => if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) + } + + object Empty extends LazyList[Nothing](() => None) + + object #:: { + def unapply[A](s: LazyList[A]): Evaluated[A] = s.force + } + } + + // ------------------ 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 IterablePolyTransforms[Char, List] + with Buildable[Char, String] { + protected def coll = new StringView(s) def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { val sb = new StringBuilder - for (ch <- coll) sb.append(ch) - sb.toString + for (ch <- coll) sb += ch + sb.result } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + + protected[this] def newBuilder = new StringBuilder + + /** 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.append(f(ch)) - sb.toString + 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.append(f(ch)) - sb.toString + 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.append(ch) - sb.toString + 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 RandomAccessView[Char] { + 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] { val start = 0 val end = s.length def apply(n: Int) = s.charAt(n) override def className = "StringView" } + /** 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 SeqMonoTransforms[A, Array[A]] + with Buildable[A, Array[A]] { protected def coll = new ArrayView(xs) def iterator = coll.iterator - private def fill[B](xs: Array[B], coll: Iterable[B]): Array[B] = { - val it = coll.iterator - var i = 0 - while (it.hasNext) { - xs(i) = it.next - i += 1 - } - xs - } - override def view = new ArrayView(xs) - protected def fromLikeIterable(coll: Iterable[A]): Array[A] = - fill( - java.lang.reflect.Array.newInstance(xs.getClass.getComponentType, coll.size) - .asInstanceOf[Array[A]], - coll) + def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + protected def fromLikeIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) - def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = - fill(new Array[B](coll.size), coll) + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] - def fromIterable1[B: ClassTag](coll: Iterable[B]): Array[B] = - fill(new Array[B](coll.size), coll) + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) - def map[B: ClassTag](f: A => B): Array[B] = fromIterable1(View.Map(coll, f)) + 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 RandomAccessView[A] { + case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { val start = 0 val end = xs.length def apply(n: Int) = xs(n) override def className = "ArrayView" } -/* ---------- Views -------------------------------------------------------*/ + /* ---------- Views -------------------------------------------------------*/ /** Concrete collection type: View */ trait View[+A] extends Iterable[A] with IterableLike[A, View] { override def view = this + + /** Avoid copying if source collection is already a view. */ override def fromIterable[B](c: Iterable[B]): View[B] = c match { case c: View[B] => c case _ => View.fromIterator(c.iterator) @@ -414,57 +738,76 @@ object CollectionStrawMan6 extends LowPriority { override def className = "View" } - /** View defined in terms of indexing a range */ - trait RandomAccessView[+A] extends View[A] { - def start: Int - def end: Int - def apply(i: Int): A - def iterator: Iterator[A] = new Iterator[A] { - private var current = start - def hasNext = current < end - def next: A = { - val r = apply(current) - current += 1 - r - } - } - override def knownLength = end - start max 0 - } - + /** This object reifies operations on views as case classes */ object View { def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { def iterator = it } + + /** The empty view */ case object Empty extends View[Nothing] { def iterator = Iterator.empty override def knownLength = 0 } + + /** A view with given elements */ case class Elems[A](xs: A*) extends View[A] { def iterator = Iterator(xs: _*) override def knownLength = xs.length } + + /** A view that filters an underlying collection. */ case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { def iterator = underlying.iterator.filter(p) } + + /** A view that partitions an underlying collection into two views */ case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + + /** The view consisting of all elements of the underlying collection + * that satisfy `p`. + */ val left = Partitioned(this, true) + + /** The view consisting of all elements of the underlying collection + * that do not satisfy `p`. + */ val right = Partitioned(this, false) } + + /** A view representing one half of a partition. */ case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) } + + /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) override def knownLength = if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 } + + /** A view that takes leading elements of the underlying collection. */ + case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.take(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength min n else -1 + } + + /** A view that maps elements of the underlying collection. */ case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { def iterator = underlying.iterator.map(f) override def knownLength = underlying.knownLength } + + /** A view that flatmaps elements of the underlying collection. */ case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { def iterator = underlying.iterator.flatMap(f) } + + /** A view that concatenates elements of the underlying collection with the elements + * of another collection or iterator. + */ case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { def iterator = underlying.iterator ++ other override def knownLength = other match { @@ -474,6 +817,10 @@ object CollectionStrawMan6 extends LowPriority { -1 } } + + /** A view that zips elements of the underlying collection with the elements + * of another collection or iterator. + */ case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { def iterator = underlying.iterator.zip(other) override def knownLength = other match { @@ -485,6 +832,32 @@ object CollectionStrawMan6 extends LowPriority { } } + /** View defined in terms of indexing a range */ + trait IndexedView[+A] extends View[A] { self => + def start: Int + def end: Int + def apply(i: Int): A + + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + + def reverse = new IndexedView[A] { + def start = self.start + def end = self.end + def apply(i: Int) = self.apply(end - 1 - i) + } + + override def knownLength = end - start max 0 + def length = knownLength + } + /* ---------- Iterators ---------------------------------------------------*/ /** A core Iterator class */ @@ -555,6 +928,16 @@ object CollectionStrawMan6 extends LowPriority { 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) { @@ -575,7 +958,7 @@ object CollectionStrawMan6 extends LowPriority { def hasNext = false def next = throw new NoSuchElementException("next on empty iterator") } - def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { val start = 0 val end = xs.length def apply(n: Int) = xs(n) diff --git a/tests/run/colltest6.check b/tests/run/colltest6.check index 39acfa426345..baff99476931 100644 --- a/tests/run/colltest6.check +++ b/tests/run/colltest6.check @@ -100,3 +100,35 @@ List(1, 2, 3) ArrayView(1, 2, 3, a) ArrayView((1,true), (2,true), (3,true)) ArrayView(3, 2, 1) +------- +123 +123 +1 +1 +List(1, 2, 3) +? +List(2) +? +List(1, 3) +3 #:: Empty +List(3) +? +List(true, true, true) +? +List(1, -1, 2, -2, 3, -3) +? +List(1, 2, 3, 1, 2, 3) +? +List(1, 2, 3) +List(1, 2, 3) +List(1, 2, 3) +? +List(1, 2, 3, a) +? +List((1,true), (2,true), (3,true)) +? +List(3, 2, 1) +? +matched: 1, 2, ? +1 #:: 2 #:: ? +List(1, 2, 3) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 2596d2a4a4b1..b5776433ee4c 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -8,9 +8,11 @@ import annotation.tailrec class LowPriority { import CollectionStrawMan6._ - implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = - new ArrayView[T](xs) + /** Convert array to iterable via view. Lower priority than ArrayOps */ + implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = new ArrayView[T](xs) + /** Convert string to iterable via view. Lower priority than StringOps */ + implicit def stringToView(s: String): StringView = new StringView(s) } /** A strawman architecture for new collections. It contains some @@ -21,9 +23,74 @@ class LowPriority { * * For a test file, see tests/run/CollectionTests.scala. * - * Strawman6 is like strawman5, and adds arrays and some utilitity methods - * (tail, mkString). TODO: Generalize Builders. + * Strawman6 is like strawman5, and adds lazy lists (i.e. lazie streams), arrays + * and some utilitity methods (take, tail, mkString, toArray). Also, systematically + * uses builders for all strict collections. * + * Types covered in this strawman: + * + * 1. Collection base types: + * + * IterableOnce, Iterable, Seq, LinearSeq, View, IndexedView + * + * 2. Collection creator base types: + * + * FromIterable, IterableFactory, Buildable, Builder + * + * 3. Types that bundle operations: + * + * IterableOps, IterableMonoTransforms, IterablePolyTransforms, IterableLike + * SeqMonoTransforms, SeqLike + * + * 4. Concrete collection types: + * + * List, LazyList, ListBuffer, ArrayBuffer, ArrayBufferView, StringView, ArrayView + * + * 5. Decorators for existing types + * + * StringOps, ArrayOps + * + * 6. Related non collection types: + * + * Iterator, StringBuilder + * + * Operations covered in this strawman: + * + * 1. Abstract operations, or expected to be overridden: + * + * For iterables: + * + * iterator, fromIterable, fromLikeIterable, knownLength, className + * + * For sequences: + * + * apply, length + * + * For buildables: + * + * newBuilder + * + * For builders: + * + * +=, result + * + * 2. Utility methods, might be overridden for performance: + * + * Operations returning not necessarily a collection: + * + * foreach, foldLeft, foldRight, indexWhere, isEmpty, head, size, mkString + * + * Operations returning a collection of a fixed type constructor: + * + * view, to, toArray, copyToArray + * + * Type-preserving generic transforms: + * + * filter, partition, take, drop, tail, reverse + * + * Generic transforms returning collections of different element types: + * + * map, flatMap, ++, zip */ object CollectionStrawMan6 extends LowPriority { @@ -47,13 +114,21 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { + + /** The collection itself */ protected def coll: Iterable[A] = this + + /** The length of this collection, if it can be cheaply computed, -1 otherwise. */ def knownLength: Int = -1 - def size: Int = - if (knownLength >= 0) knownLength - else foldLeft(0)((len, x) => len + 1) + + /** 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 - override def toString = this.mkString(className ++ "(", ", ", ")") + + override def toString = s"$className(${mkString(", ")})" } /** Base trait for sequence collections */ @@ -62,27 +137,75 @@ object CollectionStrawMan6 extends LowPriority { def length: Int } - /** Base trait for strict collections */ - trait Buildable[+A, +To <: Iterable[A]] extends Iterable[A] { - protected[this] def newBuilder: Builder[A, To] - override def partition(p: A => Boolean): (To, To) = { + /** Base trait for linearly accessed sequences */ + trait LinearSeq[+A] extends Seq[A] { self => + + 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 } + } + + def apply(i: Int): A = { + require(!isEmpty) + if (i == 0) head else tail.apply(i - 1) + } + + def length: Int = if (isEmpty) 0 else 1 + tail.length + + /** Optimized version of `drop` that avoids copying */ + final override def drop(n: Int) = { + var current: Seq[A] = this + var i = n + while (i > 0) { + current = current.tail + i -= 1 + } + current + } + } + + /** Base trait for strict collections that can be built using a builder. + * @param A the element type of the collection + * @param 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 - iterator.foreach(x => (if (p(x)) l else r) += x) + 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] { + 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 ----------------------------------- */ @@ -103,98 +226,181 @@ object CollectionStrawMan6 extends LowPriority { 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. + */ protected[this] def fromLikeIterable(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 + extends IterableLike[A, C] + with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote + /** Operations over iterables. No operation defined here is generic in the + * type of the underlying collection. + */ trait IterableOps[+A] extends Any { - def iterator: Iterator[A] + 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 - def head: A = iterator.next + + /** The first element of the collection. */ + def head: A = iterator.next() + + /** The number of elements in this collection. */ + def size: Int = + if (coll.knownLength >= 0) coll.knownLength else foldLeft(0)((len, x) => len + 1) + + /** A view representing the elements of this collection. */ def view: View[A] = View.fromIterator(iterator) - def mkString(lead: String, sep: String, follow: String): String = { + + /** 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(lead) + val b = new StringBuilder() foreach { elem => - if (!first) b.append(sep) + if (!first) b ++= sep first = false - b.append(elem) + b ++= String.valueOf(elem) } - b.append(follow).toString + b.result + } + + /** 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 (coll.knownLength >= 0) copyToArray(new Array[B](coll.knownLength), 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 } } + /** 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 fromLikeIterable(coll: Iterable[A]): Repr + + /** All elements satisfying predicate `p` */ def filter(p: A => Boolean): Repr = fromLikeIterable(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) (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) } + + /** A collection containing the first `n` elements of this collection. */ + def take(n: Int): Repr = fromLikeIterable(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 = fromLikeIterable(View.Drop(coll, n)) + + /** The rest of the collection without its first element. */ def tail: Repr = drop(1) - 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) } + /** 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 = { - var xs: List[A] = Nil - var it = coll.iterator - while (it.hasNext) xs = new Cons(it.next, xs) - fromLikeIterable(xs) + def reverse: Repr = coll.view match { + case v: IndexedView[A] => fromLikeIterable(v.reverse) + case _ => + var xs: List[A] = Nil + var it = coll.iterator + while (it.hasNext) xs = new Cons(it.next(), xs) + fromLikeIterable(xs) } } /* --------- Concrete collection types ------------------------------- */ /** Concrete collection type: List */ - sealed trait List[+A] extends Seq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + sealed trait List[+A] extends LinearSeq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => + def isEmpty: Boolean def head: A def tail: List[A] - def iterator = new Iterator[A] { - private[this] var current = self - def hasNext = !current.isEmpty - def next = { val r = current.head; current = current.tail; r } - } + def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c) - def apply(i: Int): A = { - require(!isEmpty) - if (i == 0) head else tail.apply(i - 1) - } - def length: Int = - if (isEmpty) 0 else 1 + tail.length - protected[this] def newBuilder = new ListBuffer[A] + + protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList) + + /** Prepend operation that avoids copying this list */ def ++:[B >: A](prefix: List[B]): List[B] = if (prefix.isEmpty) this else Cons(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) } - @tailrec final override def drop(n: Int) = - if (n > 0) tail.drop(n - 1) else this + override def className = "List" } @@ -214,29 +420,43 @@ object CollectionStrawMan6 extends LowPriority { object List extends IterableFactory[List] { def fromIterable[B](coll: Iterable[B]): List[B] = coll match { case coll: List[B] => coll - case _ => ListBuffer.fromIterable(coll).result + case _ => ListBuffer.fromIterable(coll).toList } } /** Concrete collection type: ListBuffer */ - class ListBuffer[A] extends Seq[A] with SeqLike[A, ListBuffer] with Builder[A, List[A]] { + 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 + def iterator = first.iterator + def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) + def apply(i: Int) = first.apply(i) + def length = first.length + protected[this] def newBuilder = new ListBuffer[A] + private def copyElems(): Unit = { val buf = ListBuffer.fromIterable(result) first = buf.first last = buf.last aliased = false } - def result = { + + /** Convert to list; avoids copying where possible. */ + def toList = { aliased = true first } + def +=(elem: A) = { if (aliased) copyElems() val last1 = Cons(elem, Nil) @@ -247,6 +467,9 @@ object CollectionStrawMan6 extends LowPriority { last = last1 this } + + def result = this + override def className = "ListBuffer" } @@ -256,18 +479,31 @@ object CollectionStrawMan6 extends LowPriority { /** Concrete collection type: ArrayBuffer */ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) - extends Seq[A] with SeqLike[A, ArrayBuffer] with Builder[A, ArrayBuffer[A]] { + 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 knownLength = 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) { @@ -285,8 +521,13 @@ object CollectionStrawMan6 extends LowPriority { 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) @@ -296,10 +537,18 @@ object CollectionStrawMan6 extends LowPriority { 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.knownLength >= 0) { val elems = new Array[AnyRef](coll.knownLength) @@ -315,98 +564,173 @@ object CollectionStrawMan6 extends LowPriority { } } - class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends RandomAccessView[A] { + class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { def apply(n: Int) = elems(start + n).asInstanceOf[A] override def className = "ArrayBufferView" } - /** Concrete collection type: String */ + class LazyList[+A](expr: () => LazyList.Evaluated[A]) + extends LinearSeq[A] with SeqLike[A, LazyList] { self => + 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, self))) + + 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 "?" + } + + object LazyList extends IterableFactory[LazyList] { + + type Evaluated[+A] = Option[(A, LazyList[A])] + + def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { + case coll: LazyList[B] => coll + case _ => new LazyList(() => if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) + } + + object Empty extends LazyList[Nothing](() => None) + + object #:: { + def unapply[A](s: LazyList[A]): Evaluated[A] = s.force + } + } + + // ------------------ 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 IterablePolyTransforms[Char, List] + with Buildable[Char, String] { + protected def coll = new StringView(s) def iterator = coll.iterator + protected def fromLikeIterable(coll: Iterable[Char]): String = { val sb = new StringBuilder - for (ch <- coll) sb.append(ch) - sb.toString + for (ch <- coll) sb += ch + sb.result } + def fromIterable[B](coll: Iterable[B]): List[B] = List.fromIterable(coll) + + protected[this] def newBuilder = new StringBuilder + + /** 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.append(f(ch)) - sb.toString + 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.append(f(ch)) - sb.toString + 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.append(ch) - sb.toString + 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 RandomAccessView[Char] { + 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] { val start = 0 val end = s.length def apply(n: Int) = s.charAt(n) override def className = "StringView" } + /** 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 SeqMonoTransforms[A, Array[A]] + with Buildable[A, Array[A]] { protected def coll = new ArrayView(xs) def iterator = coll.iterator - private def fill[B](xs: Array[B], coll: Iterable[B]): Array[B] = { - val it = coll.iterator - var i = 0 - while (it.hasNext) { - xs(i) = it.next - i += 1 - } - xs - } - override def view = new ArrayView(xs) - protected def fromLikeIterable(coll: Iterable[A]): Array[A] = - fill( - java.lang.reflect.Array.newInstance(xs.getClass.getComponentType, coll.size) - .asInstanceOf[Array[A]], - coll) + def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + protected def fromLikeIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) - def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = - fill(new Array[B](coll.size), coll) + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] - def fromIterable1[B: ClassTag](coll: Iterable[B]): Array[B] = - fill(new Array[B](coll.size), coll) + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) - def map[B: ClassTag](f: A => B): Array[B] = fromIterable1(View.Map(coll, f)) + 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 RandomAccessView[A] { + case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { val start = 0 val end = xs.length def apply(n: Int) = xs(n) override def className = "ArrayView" } -/* ---------- Views -------------------------------------------------------*/ + /* ---------- Views -------------------------------------------------------*/ /** Concrete collection type: View */ trait View[+A] extends Iterable[A] with IterableLike[A, View] { override def view = this + + /** Avoid copying if source collection is already a view. */ override def fromIterable[B](c: Iterable[B]): View[B] = c match { case c: View[B] => c case _ => View.fromIterator(c.iterator) @@ -414,57 +738,76 @@ object CollectionStrawMan6 extends LowPriority { override def className = "View" } - /** View defined in terms of indexing a range */ - trait RandomAccessView[+A] extends View[A] { - def start: Int - def end: Int - def apply(i: Int): A - def iterator: Iterator[A] = new Iterator[A] { - private var current = start - def hasNext = current < end - def next: A = { - val r = apply(current) - current += 1 - r - } - } - override def knownLength = end - start max 0 - } - + /** This object reifies operations on views as case classes */ object View { def fromIterator[A](it: => Iterator[A]): View[A] = new View[A] { def iterator = it } + + /** The empty view */ case object Empty extends View[Nothing] { def iterator = Iterator.empty override def knownLength = 0 } + + /** A view with given elements */ case class Elems[A](xs: A*) extends View[A] { def iterator = Iterator(xs: _*) override def knownLength = xs.length } + + /** A view that filters an underlying collection. */ case class Filter[A](val underlying: Iterable[A], p: A => Boolean) extends View[A] { def iterator = underlying.iterator.filter(p) } + + /** A view that partitions an underlying collection into two views */ case class Partition[A](val underlying: Iterable[A], p: A => Boolean) { + + /** The view consisting of all elements of the underlying collection + * that satisfy `p`. + */ val left = Partitioned(this, true) + + /** The view consisting of all elements of the underlying collection + * that do not satisfy `p`. + */ val right = Partitioned(this, false) } + + /** A view representing one half of a partition. */ case class Partitioned[A](partition: Partition[A], cond: Boolean) extends View[A] { def iterator = partition.underlying.iterator.filter(x => partition.p(x) == cond) } + + /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) override def knownLength = if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 } + + /** A view that takes leading elements of the underlying collection. */ + case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { + def iterator = underlying.iterator.take(n) + override def knownLength = + if (underlying.knownLength >= 0) underlying.knownLength min n else -1 + } + + /** A view that maps elements of the underlying collection. */ case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { def iterator = underlying.iterator.map(f) override def knownLength = underlying.knownLength } + + /** A view that flatmaps elements of the underlying collection. */ case class FlatMap[A, B](underlying: Iterable[A], f: A => IterableOnce[B]) extends View[B] { def iterator = underlying.iterator.flatMap(f) } + + /** A view that concatenates elements of the underlying collection with the elements + * of another collection or iterator. + */ case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { def iterator = underlying.iterator ++ other override def knownLength = other match { @@ -474,6 +817,10 @@ object CollectionStrawMan6 extends LowPriority { -1 } } + + /** A view that zips elements of the underlying collection with the elements + * of another collection or iterator. + */ case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { def iterator = underlying.iterator.zip(other) override def knownLength = other match { @@ -485,6 +832,32 @@ object CollectionStrawMan6 extends LowPriority { } } + /** View defined in terms of indexing a range */ + trait IndexedView[+A] extends View[A] { self => + def start: Int + def end: Int + def apply(i: Int): A + + def iterator: Iterator[A] = new Iterator[A] { + private var current = start + def hasNext = current < end + def next: A = { + val r = apply(current) + current += 1 + r + } + } + + def reverse = new IndexedView[A] { + def start = self.start + def end = self.end + def apply(i: Int) = self.apply(end - 1 - i) + } + + override def knownLength = end - start max 0 + def length = knownLength + } + /* ---------- Iterators ---------------------------------------------------*/ /** A core Iterator class */ @@ -555,6 +928,16 @@ object CollectionStrawMan6 extends LowPriority { 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) { @@ -575,7 +958,7 @@ object CollectionStrawMan6 extends LowPriority { def hasNext = false def next = throw new NoSuchElementException("next on empty iterator") } - def apply[A](xs: A*): Iterator[A] = new RandomAccessView[A] { + def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { val start = 0 val end = xs.length def apply(n: Int) = xs(n) diff --git a/tests/run/colltest6/CollectionTests_2.scala b/tests/run/colltest6/CollectionTests_2.scala index 659bfb9536a3..3304ffb22ff7 100644 --- a/tests/run/colltest6/CollectionTests_2.scala +++ b/tests/run/colltest6/CollectionTests_2.scala @@ -49,7 +49,7 @@ object Test { println(xs9) println(xs10) println(xs11) - println(xs12) + println(xs12) println(xs13) println(xs14) println(xs15) @@ -211,6 +211,79 @@ object Test { println(xs16.view) } + def lazyListOps(xs: Seq[Int]) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + 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) + val ys6: Seq[Int] = xs6 + val ys7: Seq[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Seq[Int] = xs8 + val xs9 = xs.map(_ >= 0) + val ys9: Seq[Boolean] = xs9 + val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val ys10: Seq[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Seq[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: Seq[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: Seq[Int] = xs13 + val xs14 = xs ++ Cons("a", Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Seq[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs6.to(List)) + println(xs7) + println(xs7.to(List)) + println(xs8) + println(xs8.to(List)) + println(xs9) + println(xs9.to(List)) + println(xs10) + println(xs10.to(List)) + println(xs11) + println(xs11.to(List)) + println(xs12) + println(xs12.to(List)) + println(xs13) + println(xs13.to(List)) + println(xs14) + println(xs14.to(List)) + println(xs15) + println(xs15.to(List)) + println(xs16) + println(xs16.to(List)) + + import LazyList.#:: + val xs17 = 1 #:: 2 #:: 3 #:: LazyList.Empty + println(xs17) + xs17 match { + case a #:: b #:: xs18 => + println(s"matched: $a, $b, $xs18") + case _ => + } + println(xs17) + println(xs17.to(List)) + } + def main(args: Array[String]) = { val ints = Cons(1, Cons(2, Cons(3, Nil))) val intsBuf = ints.to(ArrayBuffer) @@ -222,5 +295,6 @@ object Test { viewOps(intsView) stringOps("abc") arrayOps(Array(1, 2, 3)) + lazyListOps(LazyList(1, 2, 3)) } } From 568e48961e423a1dd1425c2c65bc129edba3f700 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 10:06:35 +0200 Subject: [PATCH 03/17] Improve drop By making LinearSeq an IterableLike, we can use tail-recursion on drop. --- .../collections/CollectionStrawMan6.scala | 15 ++++----------- tests/run/colltest6/CollectionStrawMan6_1.scala | 13 +++---------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index b5776433ee4c..6be02177bf6f 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -138,7 +138,7 @@ object CollectionStrawMan6 extends LowPriority { } /** Base trait for linearly accessed sequences */ - trait LinearSeq[+A] extends Seq[A] { self => + trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => def iterator = new Iterator[A] { private[this] var current: Seq[A] = self @@ -154,15 +154,8 @@ object CollectionStrawMan6 extends LowPriority { def length: Int = if (isEmpty) 0 else 1 + tail.length /** Optimized version of `drop` that avoids copying */ - final override def drop(n: Int) = { - var current: Seq[A] = this - var i = n - while (i > 0) { - current = current.tail - i -= 1 - } - current - } + @tailrec final override def drop(n: Int) = + if (n <= 0) this else tail.drop(n - 1) } /** Base trait for strict collections that can be built using a builder. @@ -598,7 +591,7 @@ object CollectionStrawMan6 extends LowPriority { case None => "Empty" case Some((hd, tl)) => s"$hd #:: $tl" } - else "?" + else "LazyList(?)" } object LazyList extends IterableFactory[LazyList] { diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index b5776433ee4c..1cd84133693a 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -138,7 +138,7 @@ object CollectionStrawMan6 extends LowPriority { } /** Base trait for linearly accessed sequences */ - trait LinearSeq[+A] extends Seq[A] { self => + trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => def iterator = new Iterator[A] { private[this] var current: Seq[A] = self @@ -154,15 +154,8 @@ object CollectionStrawMan6 extends LowPriority { def length: Int = if (isEmpty) 0 else 1 + tail.length /** Optimized version of `drop` that avoids copying */ - final override def drop(n: Int) = { - var current: Seq[A] = this - var i = n - while (i > 0) { - current = current.tail - i -= 1 - } - current - } + @tailrec final override def drop(n: Int) = + if (n <= 0) this else tail.drop(n - 1) } /** Base trait for strict collections that can be built using a builder. From 0d44e9ea121fc664a97791b6ad29631cd3aba8f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 12:40:29 +0200 Subject: [PATCH 04/17] Fix substDealias substDealias did not follow aliases when the prefix of a typeref changed under substitution. This was exhibited by a bug in extensionMethods which was first discovered in CollectionStrawMan6 and was minimized in extmethods. --- src/dotty/tools/dotc/core/Substituters.scala | 13 ++++++------- tests/pos/extmethods.scala | 11 +++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/core/Substituters.scala b/src/dotty/tools/dotc/core/Substituters.scala index 0d1c78e2f2d0..23683608acb3 100644 --- a/src/dotty/tools/dotc/core/Substituters.scala +++ b/src/dotty/tools/dotc/core/Substituters.scala @@ -102,14 +102,13 @@ trait Substituters { this: Context => } if (sym.isStatic && !existsStatic(from)) tp else { - val prefix1 = substDealias(tp.prefix, from, to, theMap) - if (prefix1 ne tp.prefix) tp.derivedSelect(prefix1) - else if (sym.isAliasType) { - val hi = sym.info.bounds.hi - val hi1 = substDealias(hi, from, to, theMap) - if (hi1 eq hi) tp else hi1 + tp.info match { + case TypeAlias(alias) => + val alias1 = substDealias(alias, from, to, theMap) + if (alias1 ne alias) return alias1 + case _ => } - else tp + tp.derivedSelect(substDealias(tp.prefix, from, to, theMap)) } case _: ThisType | _: BoundType | NoPrefix => tp diff --git a/tests/pos/extmethods.scala b/tests/pos/extmethods.scala index cac1c4ec10aa..fe95a1c79b04 100644 --- a/tests/pos/extmethods.scala +++ b/tests/pos/extmethods.scala @@ -9,3 +9,14 @@ class Foo[+A <: AnyRef](val xs: List[A]) extends AnyVal { def baz[B >: A](x: B): List[B] = ??? } +object CollectionStrawMan { + import collection.mutable.ArrayBuffer + import reflect.ClassTag + + implicit class ArrayOps[A](val xs: Array[A]) extends AnyVal { + + def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) + } +} From aa0bf126cee8c738e7dba2494ce62b70b9aefb1e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 12:43:38 +0200 Subject: [PATCH 05/17] Rename fromLikeIterable -> fromIterableWithSameElemType Makes it clearer what it is. Also, fixed check file. --- .../collections/CollectionStrawMan6.scala | 22 ++++++++--------- tests/run/colltest6.check | 24 +++++++++---------- .../run/colltest6/CollectionStrawMan6_1.scala | 24 +++++++++---------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index 6be02177bf6f..f4d5e006b895 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -60,7 +60,7 @@ class LowPriority { * * For iterables: * - * iterator, fromIterable, fromLikeIterable, knownLength, className + * iterator, fromIterable, fromIterableWithSameElemType, knownLength, className * * For sequences: * @@ -223,7 +223,7 @@ object CollectionStrawMan6 extends LowPriority { /** Create a collection of type `C[A]` from the elements of `coll`, which has * the same element type as this collection. */ - protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) } /** Base trait for Seq operations */ @@ -309,10 +309,10 @@ object CollectionStrawMan6 extends LowPriority { */ trait IterableMonoTransforms[+A, +Repr] extends Any { protected def coll: Iterable[A] - protected[this] def fromLikeIterable(coll: Iterable[A]): Repr + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): Repr /** All elements satisfying predicate `p` */ - def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, 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. @@ -323,16 +323,16 @@ object CollectionStrawMan6 extends LowPriority { */ def partition(p: A => Boolean): (Repr, Repr) = { val pn = View.Partition(coll, p) - (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + (fromIterableWithSameElemType(pn.left), fromIterableWithSameElemType(pn.right)) } /** A collection containing the first `n` elements of this collection. */ - def take(n: Int): Repr = fromLikeIterable(View.Take(coll, n)) + 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 = fromLikeIterable(View.Drop(coll, n)) + def drop(n: Int): Repr = fromIterableWithSameElemType(View.Drop(coll, n)) /** The rest of the collection without its first element. */ def tail: Repr = drop(1) @@ -361,12 +361,12 @@ object CollectionStrawMan6 extends LowPriority { /** 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] => fromLikeIterable(v.reverse) + case v: IndexedView[A] => fromIterableWithSameElemType(v.reverse) case _ => var xs: List[A] = Nil var it = coll.iterator while (it.hasNext) xs = new Cons(it.next(), xs) - fromLikeIterable(xs) + fromIterableWithSameElemType(xs) } } @@ -623,7 +623,7 @@ object CollectionStrawMan6 extends LowPriority { protected def coll = new StringView(s) def iterator = coll.iterator - protected def fromLikeIterable(coll: Iterable[Char]): String = { + protected def fromIterableWithSameElemType(coll: Iterable[Char]): String = { val sb = new StringBuilder for (ch <- coll) sb += ch sb.result @@ -698,7 +698,7 @@ object CollectionStrawMan6 extends LowPriority { def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) - protected def fromLikeIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) + protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] diff --git a/tests/run/colltest6.check b/tests/run/colltest6.check index baff99476931..e62f29f09d34 100644 --- a/tests/run/colltest6.check +++ b/tests/run/colltest6.check @@ -106,29 +106,29 @@ ArrayView(3, 2, 1) 1 1 List(1, 2, 3) -? +LazyList(?) List(2) -? +LazyList(?) List(1, 3) 3 #:: Empty List(3) -? +LazyList(?) List(true, true, true) -? +LazyList(?) List(1, -1, 2, -2, 3, -3) -? +LazyList(?) List(1, 2, 3, 1, 2, 3) -? +LazyList(?) List(1, 2, 3) List(1, 2, 3) List(1, 2, 3) -? +LazyList(?) List(1, 2, 3, a) -? +LazyList(?) List((1,true), (2,true), (3,true)) -? +LazyList(?) List(3, 2, 1) -? -matched: 1, 2, ? -1 #:: 2 #:: ? +LazyList(?) +matched: 1, 2, LazyList(?) +1 #:: 2 #:: LazyList(?) List(1, 2, 3) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 1cd84133693a..f4d5e006b895 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -60,7 +60,7 @@ class LowPriority { * * For iterables: * - * iterator, fromIterable, fromLikeIterable, knownLength, className + * iterator, fromIterable, fromIterableWithSameElemType, knownLength, className * * For sequences: * @@ -223,7 +223,7 @@ object CollectionStrawMan6 extends LowPriority { /** Create a collection of type `C[A]` from the elements of `coll`, which has * the same element type as this collection. */ - protected[this] def fromLikeIterable(coll: Iterable[A]): C[A] = fromIterable(coll) + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) } /** Base trait for Seq operations */ @@ -309,10 +309,10 @@ object CollectionStrawMan6 extends LowPriority { */ trait IterableMonoTransforms[+A, +Repr] extends Any { protected def coll: Iterable[A] - protected[this] def fromLikeIterable(coll: Iterable[A]): Repr + protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): Repr /** All elements satisfying predicate `p` */ - def filter(p: A => Boolean): Repr = fromLikeIterable(View.Filter(coll, 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. @@ -323,16 +323,16 @@ object CollectionStrawMan6 extends LowPriority { */ def partition(p: A => Boolean): (Repr, Repr) = { val pn = View.Partition(coll, p) - (fromLikeIterable(pn.left), fromLikeIterable(pn.right)) + (fromIterableWithSameElemType(pn.left), fromIterableWithSameElemType(pn.right)) } /** A collection containing the first `n` elements of this collection. */ - def take(n: Int): Repr = fromLikeIterable(View.Take(coll, n)) + 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 = fromLikeIterable(View.Drop(coll, n)) + def drop(n: Int): Repr = fromIterableWithSameElemType(View.Drop(coll, n)) /** The rest of the collection without its first element. */ def tail: Repr = drop(1) @@ -361,12 +361,12 @@ object CollectionStrawMan6 extends LowPriority { /** 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] => fromLikeIterable(v.reverse) + case v: IndexedView[A] => fromIterableWithSameElemType(v.reverse) case _ => var xs: List[A] = Nil var it = coll.iterator while (it.hasNext) xs = new Cons(it.next(), xs) - fromLikeIterable(xs) + fromIterableWithSameElemType(xs) } } @@ -591,7 +591,7 @@ object CollectionStrawMan6 extends LowPriority { case None => "Empty" case Some((hd, tl)) => s"$hd #:: $tl" } - else "?" + else "LazyList(?)" } object LazyList extends IterableFactory[LazyList] { @@ -623,7 +623,7 @@ object CollectionStrawMan6 extends LowPriority { protected def coll = new StringView(s) def iterator = coll.iterator - protected def fromLikeIterable(coll: Iterable[Char]): String = { + protected def fromIterableWithSameElemType(coll: Iterable[Char]): String = { val sb = new StringBuilder for (ch <- coll) sb += ch sb.result @@ -698,7 +698,7 @@ object CollectionStrawMan6 extends LowPriority { def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) - protected def fromLikeIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) + protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] From 2e725908c3bd340daf140906885fdb43fba13a0f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 14:22:59 +0200 Subject: [PATCH 06/17] Fix problem related to cbn parameters in supercalls The closures generated by elimByName did not get the InSuperCall flag set. This caused problems in lambda lift which led to a verify error for the new version CollectionStrawMan6. That version replaces explicit function parameters in class LazyList by by-name parameters. Also: Clarify logic for liftLocals in LambdaLift (liftLocals caused the immediate problem but was in the end not to blame). --- src/dotty/tools/dotc/transform/ElimByName.scala | 3 ++- src/dotty/tools/dotc/transform/LambdaLift.scala | 16 +++++++++------- .../collections/CollectionStrawMan6.scala | 10 +++++----- tests/run/colltest6/CollectionStrawMan6_1.scala | 10 +++++----- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index b65a46249d04..1922272612a2 100644 --- a/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/src/dotty/tools/dotc/transform/ElimByName.scala @@ -77,8 +77,9 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform if qual.tpe.derivesFrom(defn.FunctionClass(0)) && isPureExpr(qual) => qual case _ => + val inSuper = if (ctx.mode.is(Mode.InSuperCall)) InSuperCall else EmptyFlags val meth = ctx.newSymbol( - ctx.owner, nme.ANON_FUN, Synthetic | Method, MethodType(Nil, Nil, argType)) + ctx.owner, nme.ANON_FUN, Synthetic | Method | inSuper, MethodType(Nil, Nil, argType)) Closure(meth, _ => arg.changeOwner(ctx.owner, meth)) } ref(defn.dummyApply).appliedToType(argType).appliedTo(argFun) diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala index 5fbe0343f12e..18b030913ef0 100644 --- a/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -364,13 +364,15 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform if (lOwner is Package) { val encClass = local.enclosingClass val topClass = local.topLevelClass - // member of a static object - if (encClass.isStatic && encClass.isProperlyContainedIn(topClass)) { - // though the second condition seems weird, it's not true for symbols which are defined in some - // weird combinations of super calls. - (encClass, EmptyFlags) - } else if (encClass.is(ModuleClass, butNot = Package) && encClass.isStatic) // needed to not cause deadlocks in classloader. see t5375.scala - (encClass, EmptyFlags) + val preferEncClass = + encClass.isStatic && + // non-static classes can capture owners, so should be avoided + (encClass.isProperlyContainedIn(topClass) || + // can be false for symbols which are defined in some weird combination of supercalls. + encClass.is(ModuleClass, butNot = Package) + // needed to not cause deadlocks in classloader. see t5375.scala + ) + if (preferEncClass) (encClass, EmptyFlags) else (topClass, JavaStatic) } else (lOwner, EmptyFlags) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index f4d5e006b895..5889782d6468 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -562,14 +562,14 @@ object CollectionStrawMan6 extends LowPriority { override def className = "ArrayBufferView" } - class LazyList[+A](expr: () => LazyList.Evaluated[A]) + class LazyList[+A](expr: => LazyList.Evaluated[A]) extends LinearSeq[A] with SeqLike[A, LazyList] { self => private[this] var evaluated = false private[this] var result: LazyList.Evaluated[A] = _ def force: LazyList.Evaluated[A] = { if (!evaluated) { - result = expr() + result = expr evaluated = true } result @@ -579,7 +579,7 @@ object CollectionStrawMan6 extends LowPriority { override def head = force.get._1 override def tail = force.get._2 - def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(() => Some((elem, self))) + def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, self))) def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) @@ -600,10 +600,10 @@ object CollectionStrawMan6 extends LowPriority { def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { case coll: LazyList[B] => coll - case _ => new LazyList(() => if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) + case _ => new LazyList(if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) } - object Empty extends LazyList[Nothing](() => None) + object Empty extends LazyList[Nothing](None) object #:: { def unapply[A](s: LazyList[A]): Evaluated[A] = s.force diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index f4d5e006b895..5889782d6468 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -562,14 +562,14 @@ object CollectionStrawMan6 extends LowPriority { override def className = "ArrayBufferView" } - class LazyList[+A](expr: () => LazyList.Evaluated[A]) + class LazyList[+A](expr: => LazyList.Evaluated[A]) extends LinearSeq[A] with SeqLike[A, LazyList] { self => private[this] var evaluated = false private[this] var result: LazyList.Evaluated[A] = _ def force: LazyList.Evaluated[A] = { if (!evaluated) { - result = expr() + result = expr evaluated = true } result @@ -579,7 +579,7 @@ object CollectionStrawMan6 extends LowPriority { override def head = force.get._1 override def tail = force.get._2 - def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(() => Some((elem, self))) + def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, self))) def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) @@ -600,10 +600,10 @@ object CollectionStrawMan6 extends LowPriority { def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { case coll: LazyList[B] => coll - case _ => new LazyList(() => if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) + case _ => new LazyList(if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) } - object Empty extends LazyList[Nothing](() => None) + object Empty extends LazyList[Nothing](None) object #:: { def unapply[A](s: LazyList[A]): Evaluated[A] = s.force From 2af7ea0c44b4d274503921911d80a5c7baab06ab Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 14:44:40 +0200 Subject: [PATCH 07/17] Move test from pos to run Test generated code before but fails with verify error at runtime. Here's the message: Exception in thread "main" java.lang.VerifyError: Bad type on operand stack Exception Details: Location: D$.()V @2: invokedynamic Reason: Type uninitializedThis (current frame, stack[1]) is not assignable to 'D$' Current Frame: bci: @2 flags: { flagThisUninit } locals: { uninitializedThis } stack: { uninitializedThis, uninitializedThis } Bytecode: 0x0000000: 2a2a ba00 1f00 00b7 0022 2ab3 0024 b1 at Test$.main(t3048.scala:13) at Test.main(t3048.scala) With the fix in last commit, test causes backend to crash with java.lang.AssertionError: assertion failed: val at scala.Predef$.assert(Predef.scala:165) at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.assertClassNotArray(BCodeHelpers.scala:214) at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.assertClassNotArrayNotPrimitive(BCodeHelpers.scala:219) at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.getClassBTypeAndRegisterInnerClass(BCodeHelpers.scala:238) at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.getClassBTypeAndRegisterInnerClass(BCodeSkelBuilder.scala:51) at scala.tools.nsc.backend.jvm.BCodeHelpers$BCInnerClassGen$class.internalName(BCodeHelpers.scala:210) --- tests/{pos => run}/t3048.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) rename tests/{pos => run}/t3048.scala (51%) diff --git a/tests/pos/t3048.scala b/tests/run/t3048.scala similarity index 51% rename from tests/pos/t3048.scala rename to tests/run/t3048.scala index dc056ecba2ca..8762047afb73 100644 --- a/tests/pos/t3048.scala +++ b/tests/run/t3048.scala @@ -1,8 +1,17 @@ class B object C extends B -class F[T <: B](cons: => T) +class F[T <: B](cons: => T) { + def f = cons +} class F2[T <: B](cons: => T) extends F(cons) object D extends F2(C) // works object E extends F2(new B {}) + +object Test { + def main(args: Array[String]): Unit = { + D.f + E.f + } +} From d7b1c0e018027b822266961beeeab3d87f02cbb0 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Wed, 27 Jul 2016 16:19:53 +0200 Subject: [PATCH 08/17] Quick-fix eclosingMethod attribute generation for t3048.scala It's a funny interaction between: elim-by-name(and erasure specifically); lift-static; supercalls. object E extends F2(new B {}) Here we have an anonymous class new B {} that looks like it is created by erasure. For some reason this class forgets the link to original anonymous class: SymDenot(E$annon1).initial.phase == erasure. I guess this is a bug. Additionally, the owner of E$annon1 is an anonymous method inside E, that is inSuperCall and thus we have an anonymous nested class that has enclosingClass be package. This class looks like a top-level anonymous class breaking a lot of assumptions in shared backend and taking multiple branches in unexpected ways. I'm not sure that this is a proper fix. I assume there's a bigger bug around, but I don't quite understand it right now and I need to work on other stuff. Making a quick fix to unblock @odersky. --- .../tools/backend/jvm/DottyBackendInterface.scala | 4 +++- src/dotty/tools/dotc/core/SymDenotations.scala | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index f42a8eee2b61..30934605bd21 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -648,12 +648,14 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma originalOwner } def originalOwner: Symbol = { + // used to populate the EnclosingMethod attribute. + // it is very tricky in presence of classes(and annonymous classes) defined inside supper calls. try { if (sym.exists) { val original = toDenot(sym).initial val validity = original.validFor val shiftedContext = ctx.withPhase(validity.phaseId) - val r = toDenot(sym)(shiftedContext).maybeOwner.enclosingClass(shiftedContext) + val r = toDenot(sym)(shiftedContext).maybeOwner.lexicallyEnclosingClass(shiftedContext) r } else NoSymbol } catch { diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 16c77ac3032e..ebb061c6bbd0 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -844,6 +844,19 @@ object SymDenotations { enclClass(symbol, false) } + /** A class that in sourlce code would be lexically enclosing */ + final def lexicallyEnclosingClass(implicit ctx: Context): Symbol = { + def enclClass(sym: Symbol): Symbol = { + if (!sym.exists) + NoSymbol + else if (sym.isClass) + sym + else + enclClass(sym.owner) + } + enclClass(symbol) + } + /** A symbol is effectively final if it cannot be overridden in a subclass */ final def isEffectivelyFinal(implicit ctx: Context): Boolean = is(PrivateOrFinal) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass From 87cff22595d70b9b85b4420e478562b99fab647b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 16:37:00 +0200 Subject: [PATCH 09/17] Simplify lexicallyEnclosingClass --- src/dotty/tools/dotc/core/SymDenotations.scala | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ebb061c6bbd0..9d96f2b15f6f 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -844,18 +844,9 @@ object SymDenotations { enclClass(symbol, false) } - /** A class that in sourlce code would be lexically enclosing */ - final def lexicallyEnclosingClass(implicit ctx: Context): Symbol = { - def enclClass(sym: Symbol): Symbol = { - if (!sym.exists) - NoSymbol - else if (sym.isClass) - sym - else - enclClass(sym.owner) - } - enclClass(symbol) - } + /** A class that in source code would be lexically enclosing */ + final def lexicallyEnclosingClass(implicit ctx: Context): Symbol = + if (!exists || isClass) symbol else owner.lexicallyEnclosingClass /** A symbol is effectively final if it cannot be overridden in a subclass */ final def isEffectivelyFinal(implicit ctx: Context): Boolean = From a591802a29b80df6f32fc4f8090da518b5410602 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 27 Jul 2016 23:46:30 +0200 Subject: [PATCH 10/17] Make colltest6 self-contained Following the other colltests, put each in a separate package. --- tests/run/colltest6/CollectionStrawMan6_1.scala | 1 + tests/run/colltest6/CollectionTests_2.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 5889782d6468..9f29410adf31 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -1,3 +1,4 @@ +package colltest6 package strawman.collections import Predef.{augmentString => _, wrapString => _, _} diff --git a/tests/run/colltest6/CollectionTests_2.scala b/tests/run/colltest6/CollectionTests_2.scala index 3304ffb22ff7..f139fa1510a5 100644 --- a/tests/run/colltest6/CollectionTests_2.scala +++ b/tests/run/colltest6/CollectionTests_2.scala @@ -2,7 +2,7 @@ import Predef.{augmentString => _, wrapString => _, intArrayOps => _, booleanArr import scala.reflect.ClassTag object Test { - import strawman.collections._ + import colltest6.strawman.collections._ import CollectionStrawMan6._ def seqOps(xs: Seq[Int]) = { From 09b81e513ba703882e2350e39d5c94ba2ce46b9c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 28 Jul 2016 14:38:48 +0200 Subject: [PATCH 11/17] Tweaks to strawman - Add proper :: to lists - Move some methods to IterableOps in order to keep Iterable clean - Rename knownLength to knownSize - Add some implentations for performance and completeness --- .../collections/CollectionStrawMan6.scala | 182 ++++++++++-------- .../run/colltest6/CollectionStrawMan6_1.scala | 182 ++++++++++-------- tests/run/colltest6/CollectionTests_2.scala | 22 +-- 3 files changed, 213 insertions(+), 173 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index 5889782d6468..d3d54d89b017 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -114,22 +114,9 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { - /** The collection itself */ - protected def coll: Iterable[A] = this - - /** The length of this collection, if it can be cheaply computed, -1 otherwise. */ - def knownLength: Int = -1 - - /** 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 - - override def toString = s"$className(${mkString(", ")})" - } + protected def coll: this.type = this + } /** Base trait for sequence collections */ trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { @@ -140,18 +127,21 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for linearly accessed sequences */ trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => + /** To be overridden in implementations: */ + def isEmpty: Boolean + def head: A + def tail: LinearSeq[A] + 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 } } - def apply(i: Int): A = { - require(!isEmpty) - if (i == 0) head else tail.apply(i - 1) - } + def length: Int = iterator.length - def length: Int = if (isEmpty) 0 else 1 + tail.length + @tailrec final def apply(n: Int): A = + if (n == 0) head else tail.apply(n - 1) /** Optimized version of `drop` that avoids copying */ @tailrec final override def drop(n: Int) = @@ -221,7 +211,7 @@ object CollectionStrawMan6 extends LowPriority { 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. + * the same element type as this collection. Overridden in StringOps and ArrayOps. */ protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) } @@ -256,26 +246,20 @@ object CollectionStrawMan6 extends LowPriority { /** The first element of the collection. */ def head: A = iterator.next() - /** The number of elements in this collection. */ - def size: Int = - if (coll.knownLength >= 0) coll.knownLength else foldLeft(0)((len, x) => len + 1) + /** 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) - /** 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 - } - - /** Given a collection factory `fi` for collections of type constructor `C`, + /** 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) @@ -288,7 +272,7 @@ object CollectionStrawMan6 extends LowPriority { /** Convert collection to array. */ def toArray[B >: A: ClassTag]: Array[B] = - if (coll.knownLength >= 0) copyToArray(new Array[B](coll.knownLength), 0) + 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`. */ @@ -301,6 +285,27 @@ object CollectionStrawMan6 extends LowPriority { } 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. @@ -365,7 +370,7 @@ object CollectionStrawMan6 extends LowPriority { case _ => var xs: List[A] = Nil var it = coll.iterator - while (it.hasNext) xs = new Cons(it.next(), xs) + while (it.hasNext) xs = it.next() :: xs fromIterableWithSameElemType(xs) } } @@ -373,20 +378,22 @@ object CollectionStrawMan6 extends LowPriority { /* --------- Concrete collection types ------------------------------- */ /** Concrete collection type: List */ - sealed trait List[+A] extends LinearSeq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => - - def isEmpty: Boolean - def head: A - def tail: List[A] + 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 Cons(prefix.head, prefix.tail ++: 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 { @@ -397,7 +404,7 @@ object CollectionStrawMan6 extends LowPriority { override def className = "List" } - case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + case class :: [+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally extends List[A] { override def isEmpty = false override def head = x @@ -426,6 +433,7 @@ object CollectionStrawMan6 extends LowPriority { private var first, last: List[A] = Nil private var aliased = false + private var len = 0 def iterator = first.iterator @@ -433,7 +441,8 @@ object CollectionStrawMan6 extends LowPriority { def apply(i: Int) = first.apply(i) - def length = first.length + def length = len + override def knownSize = len protected[this] def newBuilder = new ListBuffer[A] @@ -452,12 +461,13 @@ object CollectionStrawMan6 extends LowPriority { def +=(elem: A) = { if (aliased) copyElems() - val last1 = Cons(elem, Nil) + val last1 = elem :: Nil last match { - case last: Cons[A] => last.next = last1 + case last: ::[A] => last.next = last1 case _ => first = last1 } last = last1 + len += 1 this } @@ -486,7 +496,7 @@ object CollectionStrawMan6 extends LowPriority { def apply(n: Int) = elems(start + n).asInstanceOf[A] def length = end - start - override def knownLength = length + override def knownSize = length override def view = new ArrayBufferView(elems, start, end) @@ -543,18 +553,13 @@ object CollectionStrawMan6 extends LowPriority { /** Avoid reallocation of buffer if length is known. */ def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = - if (coll.knownLength >= 0) { - val elems = new Array[AnyRef](coll.knownLength) + 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 { - val buf = new ArrayBuffer[B] - val it = coll.iterator - while (it.hasNext) buf += it.next() - buf - } + else new ArrayBuffer[B] ++= coll } class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { @@ -563,7 +568,7 @@ object CollectionStrawMan6 extends LowPriority { } class LazyList[+A](expr: => LazyList.Evaluated[A]) - extends LinearSeq[A] with SeqLike[A, LazyList] { self => + extends LinearSeq[A] with SeqLike[A, LazyList] { private[this] var evaluated = false private[this] var result: LazyList.Evaluated[A] = _ @@ -579,7 +584,7 @@ object CollectionStrawMan6 extends LowPriority { override def head = force.get._1 override def tail = force.get._2 - def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, self))) + def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this))) def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) @@ -598,16 +603,19 @@ object CollectionStrawMan6 extends LowPriority { type Evaluated[+A] = Option[(A, LazyList[A])] - def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { - case coll: LazyList[B] => coll - case _ => new LazyList(if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) - } - 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) } // ------------------ Decorators to add collection ops to existing types ----------------------- @@ -633,6 +641,10 @@ object CollectionStrawMan6 extends LowPriority { protected[this] def newBuilder = new StringBuilder + 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. */ @@ -704,6 +716,10 @@ object CollectionStrawMan6 extends LowPriority { 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)) @@ -740,13 +756,13 @@ object CollectionStrawMan6 extends LowPriority { /** The empty view */ case object Empty extends View[Nothing] { def iterator = Iterator.empty - override def knownLength = 0 + override def knownSize = 0 } /** A view with given elements */ case class Elems[A](xs: A*) extends View[A] { def iterator = Iterator(xs: _*) - override def knownLength = xs.length + override def knownSize = xs.length // should be: xs.knownSize, but A*'s are not sequences in this strawman. } /** A view that filters an underlying collection. */ @@ -776,21 +792,21 @@ object CollectionStrawMan6 extends LowPriority { /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) - override def knownLength = - if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + override def knownSize = + if (underlying.knownSize >= 0) underlying.knownSize - n max 0 else -1 } /** A view that takes leading elements of the underlying collection. */ case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.take(n) - override def knownLength = - if (underlying.knownLength >= 0) underlying.knownLength min n else -1 + override def knownSize = + if (underlying.knownSize >= 0) underlying.knownSize min n else -1 } /** A view that maps elements of the underlying collection. */ case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { def iterator = underlying.iterator.map(f) - override def knownLength = underlying.knownLength + override def knownSize = underlying.knownSize } /** A view that flatmaps elements of the underlying collection. */ @@ -803,9 +819,9 @@ object CollectionStrawMan6 extends LowPriority { */ case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { def iterator = underlying.iterator ++ other - override def knownLength = other match { - case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => - underlying.knownLength + other.knownLength + override def knownSize = other match { + case other: Iterable[_] if underlying.knownSize >= 0 && other.knownSize >= 0 => + underlying.knownSize + other.knownSize case _ => -1 } @@ -816,9 +832,9 @@ object CollectionStrawMan6 extends LowPriority { */ case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { def iterator = underlying.iterator.zip(other) - override def knownLength = other match { - case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => - underlying.knownLength min other.knownLength + override def knownSize = other match { + case other: Iterable[_] if underlying.knownSize >= 0 && other.knownSize >= 0 => + underlying.knownSize min other.knownSize case _ => -1 } @@ -847,8 +863,8 @@ object CollectionStrawMan6 extends LowPriority { def apply(i: Int) = self.apply(end - 1 - i) } - override def knownLength = end - start max 0 - def length = knownLength + def length = end - start max 0 + override def knownSize = length } /* ---------- Iterators ---------------------------------------------------*/ @@ -872,6 +888,11 @@ object CollectionStrawMan6 extends LowPriority { } -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 @@ -892,7 +913,6 @@ object CollectionStrawMan6 extends LowPriority { } else Iterator.empty.next() } - def map[B](f: A => B): Iterator[B] = new Iterator[B] { def hasNext = self.hasNext def next() = f(self.next()) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 9f29410adf31..50a7dfec310b 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -115,22 +115,9 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for generic collections */ trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { - /** The collection itself */ - protected def coll: Iterable[A] = this - - /** The length of this collection, if it can be cheaply computed, -1 otherwise. */ - def knownLength: Int = -1 - - /** 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 - - override def toString = s"$className(${mkString(", ")})" - } + protected def coll: this.type = this + } /** Base trait for sequence collections */ trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { @@ -141,18 +128,21 @@ object CollectionStrawMan6 extends LowPriority { /** Base trait for linearly accessed sequences */ trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => + /** To be overridden in implementations: */ + def isEmpty: Boolean + def head: A + def tail: LinearSeq[A] + 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 } } - def apply(i: Int): A = { - require(!isEmpty) - if (i == 0) head else tail.apply(i - 1) - } + def length: Int = iterator.length - def length: Int = if (isEmpty) 0 else 1 + tail.length + @tailrec final def apply(n: Int): A = + if (n == 0) head else tail.apply(n - 1) /** Optimized version of `drop` that avoids copying */ @tailrec final override def drop(n: Int) = @@ -222,7 +212,7 @@ object CollectionStrawMan6 extends LowPriority { 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. + * the same element type as this collection. Overridden in StringOps and ArrayOps. */ protected[this] def fromIterableWithSameElemType(coll: Iterable[A]): C[A] = fromIterable(coll) } @@ -257,26 +247,20 @@ object CollectionStrawMan6 extends LowPriority { /** The first element of the collection. */ def head: A = iterator.next() - /** The number of elements in this collection. */ - def size: Int = - if (coll.knownLength >= 0) coll.knownLength else foldLeft(0)((len, x) => len + 1) + /** 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) - /** 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 - } - - /** Given a collection factory `fi` for collections of type constructor `C`, + /** 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) @@ -289,7 +273,7 @@ object CollectionStrawMan6 extends LowPriority { /** Convert collection to array. */ def toArray[B >: A: ClassTag]: Array[B] = - if (coll.knownLength >= 0) copyToArray(new Array[B](coll.knownLength), 0) + 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`. */ @@ -302,6 +286,27 @@ object CollectionStrawMan6 extends LowPriority { } 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. @@ -366,7 +371,7 @@ object CollectionStrawMan6 extends LowPriority { case _ => var xs: List[A] = Nil var it = coll.iterator - while (it.hasNext) xs = new Cons(it.next(), xs) + while (it.hasNext) xs = it.next() :: xs fromIterableWithSameElemType(xs) } } @@ -374,20 +379,22 @@ object CollectionStrawMan6 extends LowPriority { /* --------- Concrete collection types ------------------------------- */ /** Concrete collection type: List */ - sealed trait List[+A] extends LinearSeq[A] with SeqLike[A, List] with Buildable[A, List[A]] { self => - - def isEmpty: Boolean - def head: A - def tail: List[A] + 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 Cons(prefix.head, prefix.tail ++: 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 { @@ -398,7 +405,7 @@ object CollectionStrawMan6 extends LowPriority { override def className = "List" } - case class Cons[+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + case class :: [+A](x: A, private[collections] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally extends List[A] { override def isEmpty = false override def head = x @@ -427,6 +434,7 @@ object CollectionStrawMan6 extends LowPriority { private var first, last: List[A] = Nil private var aliased = false + private var len = 0 def iterator = first.iterator @@ -434,7 +442,8 @@ object CollectionStrawMan6 extends LowPriority { def apply(i: Int) = first.apply(i) - def length = first.length + def length = len + override def knownSize = len protected[this] def newBuilder = new ListBuffer[A] @@ -453,12 +462,13 @@ object CollectionStrawMan6 extends LowPriority { def +=(elem: A) = { if (aliased) copyElems() - val last1 = Cons(elem, Nil) + val last1 = elem :: Nil last match { - case last: Cons[A] => last.next = last1 + case last: ::[A] => last.next = last1 case _ => first = last1 } last = last1 + len += 1 this } @@ -487,7 +497,7 @@ object CollectionStrawMan6 extends LowPriority { def apply(n: Int) = elems(start + n).asInstanceOf[A] def length = end - start - override def knownLength = length + override def knownSize = length override def view = new ArrayBufferView(elems, start, end) @@ -544,18 +554,13 @@ object CollectionStrawMan6 extends LowPriority { /** Avoid reallocation of buffer if length is known. */ def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = - if (coll.knownLength >= 0) { - val elems = new Array[AnyRef](coll.knownLength) + 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 { - val buf = new ArrayBuffer[B] - val it = coll.iterator - while (it.hasNext) buf += it.next() - buf - } + else new ArrayBuffer[B] ++= coll } class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { @@ -564,7 +569,7 @@ object CollectionStrawMan6 extends LowPriority { } class LazyList[+A](expr: => LazyList.Evaluated[A]) - extends LinearSeq[A] with SeqLike[A, LazyList] { self => + extends LinearSeq[A] with SeqLike[A, LazyList] { private[this] var evaluated = false private[this] var result: LazyList.Evaluated[A] = _ @@ -580,7 +585,7 @@ object CollectionStrawMan6 extends LowPriority { override def head = force.get._1 override def tail = force.get._2 - def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, self))) + def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this))) def fromIterable[B](c: Iterable[B]): LazyList[B] = LazyList.fromIterable(c) @@ -599,16 +604,19 @@ object CollectionStrawMan6 extends LowPriority { type Evaluated[+A] = Option[(A, LazyList[A])] - def fromIterable[B](coll: Iterable[B]): LazyList[B] = coll match { - case coll: LazyList[B] => coll - case _ => new LazyList(if (coll.isEmpty) None else Some((coll.head, fromIterable(coll.tail)))) - } - 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) } // ------------------ Decorators to add collection ops to existing types ----------------------- @@ -634,6 +642,10 @@ object CollectionStrawMan6 extends LowPriority { protected[this] def newBuilder = new StringBuilder + 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. */ @@ -705,6 +717,10 @@ object CollectionStrawMan6 extends LowPriority { 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)) @@ -741,13 +757,13 @@ object CollectionStrawMan6 extends LowPriority { /** The empty view */ case object Empty extends View[Nothing] { def iterator = Iterator.empty - override def knownLength = 0 + override def knownSize = 0 } /** A view with given elements */ case class Elems[A](xs: A*) extends View[A] { def iterator = Iterator(xs: _*) - override def knownLength = xs.length + override def knownSize = xs.length // should be: xs.knownSize, but A*'s are not sequences in this strawman. } /** A view that filters an underlying collection. */ @@ -777,21 +793,21 @@ object CollectionStrawMan6 extends LowPriority { /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) - override def knownLength = - if (underlying.knownLength >= 0) underlying.knownLength - n max 0 else -1 + override def knownSize = + if (underlying.knownSize >= 0) underlying.knownSize - n max 0 else -1 } /** A view that takes leading elements of the underlying collection. */ case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.take(n) - override def knownLength = - if (underlying.knownLength >= 0) underlying.knownLength min n else -1 + override def knownSize = + if (underlying.knownSize >= 0) underlying.knownSize min n else -1 } /** A view that maps elements of the underlying collection. */ case class Map[A, B](underlying: Iterable[A], f: A => B) extends View[B] { def iterator = underlying.iterator.map(f) - override def knownLength = underlying.knownLength + override def knownSize = underlying.knownSize } /** A view that flatmaps elements of the underlying collection. */ @@ -804,9 +820,9 @@ object CollectionStrawMan6 extends LowPriority { */ case class Concat[A](underlying: Iterable[A], other: IterableOnce[A]) extends View[A] { def iterator = underlying.iterator ++ other - override def knownLength = other match { - case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => - underlying.knownLength + other.knownLength + override def knownSize = other match { + case other: Iterable[_] if underlying.knownSize >= 0 && other.knownSize >= 0 => + underlying.knownSize + other.knownSize case _ => -1 } @@ -817,9 +833,9 @@ object CollectionStrawMan6 extends LowPriority { */ case class Zip[A, B](underlying: Iterable[A], other: IterableOnce[B]) extends View[(A, B)] { def iterator = underlying.iterator.zip(other) - override def knownLength = other match { - case other: Iterable[_] if underlying.knownLength >= 0 && other.knownLength >= 0 => - underlying.knownLength min other.knownLength + override def knownSize = other match { + case other: Iterable[_] if underlying.knownSize >= 0 && other.knownSize >= 0 => + underlying.knownSize min other.knownSize case _ => -1 } @@ -848,8 +864,8 @@ object CollectionStrawMan6 extends LowPriority { def apply(i: Int) = self.apply(end - 1 - i) } - override def knownLength = end - start max 0 - def length = knownLength + def length = end - start max 0 + override def knownSize = length } /* ---------- Iterators ---------------------------------------------------*/ @@ -873,6 +889,11 @@ object CollectionStrawMan6 extends LowPriority { } -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 @@ -893,7 +914,6 @@ object CollectionStrawMan6 extends LowPriority { } else Iterator.empty.next() } - def map[B](f: A => B): Iterator[B] = new Iterator[B] { def hasNext = self.hasNext def next() = f(self.next()) diff --git a/tests/run/colltest6/CollectionTests_2.scala b/tests/run/colltest6/CollectionTests_2.scala index f139fa1510a5..124a526b4f0f 100644 --- a/tests/run/colltest6/CollectionTests_2.scala +++ b/tests/run/colltest6/CollectionTests_2.scala @@ -23,7 +23,7 @@ object Test { val ys8: Seq[Int] = xs8 val xs9 = xs.map(_ >= 0) val ys9: Seq[Boolean] = xs9 - val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val xs10 = xs.flatMap(x => x :: -x :: Nil) val ys10: Seq[Int] = xs10 val xs11 = xs ++ xs val ys11: Seq[Int] = xs11 @@ -31,7 +31,7 @@ object Test { val ys12: Seq[Int] = xs12 val xs13 = Nil ++ xs val ys13: Seq[Int] = xs13 - val xs14 = xs ++ Cons("a", Nil) + val xs14 = xs ++ ("a" :: Nil) val ys14: Seq[Any] = xs14 val xs15 = xs.zip(xs9) val ys15: Seq[(Int, Boolean)] = xs15 @@ -74,7 +74,7 @@ object Test { val ys8: View[Int] = xs8 val xs9 = xs.map(_ >= 0) val ys9: View[Boolean] = xs9 - val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val xs10 = xs.flatMap(x => x :: -x :: Nil) val ys10: View[Int] = xs10 val xs11 = xs ++ xs val ys11: View[Int] = xs11 @@ -82,7 +82,7 @@ object Test { val ys12: View[Int] = xs12 val xs13 = Nil ++ xs val ys13: List[Int] = xs13 - val xs14 = xs ++ Cons("a", Nil) + val xs14 = xs ++ ("a" :: Nil) val ys14: View[Any] = xs14 val xs15 = xs.zip(xs9) val ys15: View[(Int, Boolean)] = xs15 @@ -128,13 +128,13 @@ object Test { val ys10: String = xs10 val xs11 = xs ++ xs val ys11: String = xs11 - val xs11a = xs ++ List('x', 'y') // Cons('x', Cons('y', Nil)) + val xs11a = xs ++ List('x', 'y') val ys11a: String = xs11a val xs12 = xs ++ Nil val ys12: String = xs12 val xs13 = Nil ++ xs.iterator val ys13: List[Char] = xs13 - val xs14 = xs ++ Cons("xyz", Nil) + val xs14 = xs ++ List("xyz") val ys14: Seq[Any] = xs14 val xs15 = xs.zip(xs9) val ys15: Seq[(Char, Int)] = xs15 @@ -178,7 +178,7 @@ object Test { val ys8: Array[Int] = xs8 val xs9 = ArrayOps(xs).map(_ >= 0) val ys9: Array[Boolean] = xs9 - val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val xs10 = xs.flatMap(x => List(x, -x)) val ys10: Array[Int] = xs10 val xs11 = xs ++ xs val ys11: Array[Int] = xs11 @@ -186,7 +186,7 @@ object Test { val ys12: Array[Int] = xs12 val xs13 = Nil ++ xs val ys13: List[Int] = xs13 - val xs14 = xs ++ Cons("a": Any, Nil) + val xs14 = xs ++ (("a": Any) :: Nil) val ys14: Array[Any] = xs14 val xs15 = xs.zip(xs9) val ys15: Array[(Int, Boolean)] = xs15 @@ -229,7 +229,7 @@ object Test { val ys8: Seq[Int] = xs8 val xs9 = xs.map(_ >= 0) val ys9: Seq[Boolean] = xs9 - val xs10 = xs.flatMap(x => Cons(x, Cons(-x, Nil))) + val xs10 = xs.flatMap(x => x :: -x :: Nil) val ys10: Seq[Int] = xs10 val xs11 = xs ++ xs val ys11: Seq[Int] = xs11 @@ -237,7 +237,7 @@ object Test { val ys12: Seq[Int] = xs12 val xs13 = Nil ++ xs val ys13: Seq[Int] = xs13 - val xs14 = xs ++ Cons("a", Nil) + val xs14 = xs ++ ("a" :: Nil) val ys14: Seq[Any] = xs14 val xs15 = xs.zip(xs9) val ys15: Seq[(Int, Boolean)] = xs15 @@ -285,7 +285,7 @@ object Test { } def main(args: Array[String]) = { - val ints = Cons(1, Cons(2, Cons(3, Nil))) + val ints = List(1, 2, 3) val intsBuf = ints.to(ArrayBuffer) val intsListBuf = ints.to(ListBuffer) val intsView = ints.view From 9ab6ce7c351e428d09a690cc8c841f0750fe8973 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jul 2016 12:51:03 +0200 Subject: [PATCH 12/17] 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. --- .../collections/CollectionStrawMan6.scala | 46 +++++++++++++++---- .../run/colltest6/CollectionStrawMan6_1.scala | 46 +++++++++++++++---- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index d3d54d89b017..86ec660f37cf 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -124,28 +124,36 @@ object CollectionStrawMan6 extends LowPriority { def length: Int } - /** Base trait for linearly accessed sequences */ - trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => + /** 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 - @tailrec final def apply(n: Int): A = - if (n == 0) head else tail.apply(n - 1) - - /** Optimized version of `drop` that avoids copying */ - @tailrec final override def drop(n: Int) = - if (n <= 0) this else tail.drop(n - 1) + /** `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 + } } /** Base trait for strict collections that can be built using a builder. @@ -221,6 +229,28 @@ object CollectionStrawMan6 extends LowPriority { 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. */ diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 50a7dfec310b..5d3ea49913a6 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -125,28 +125,36 @@ object CollectionStrawMan6 extends LowPriority { def length: Int } - /** Base trait for linearly accessed sequences */ - trait LinearSeq[+A] extends Seq[A] with SeqLike[A, LinearSeq] { self => + /** 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 - @tailrec final def apply(n: Int): A = - if (n == 0) head else tail.apply(n - 1) - - /** Optimized version of `drop` that avoids copying */ - @tailrec final override def drop(n: Int) = - if (n <= 0) this else tail.drop(n - 1) + /** `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 + } } /** Base trait for strict collections that can be built using a builder. @@ -222,6 +230,28 @@ object CollectionStrawMan6 extends LowPriority { 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. */ From 290200037ba9633d1dc23dc02750c1396ee11045 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jul 2016 20:37:38 +0200 Subject: [PATCH 13/17] Index members of a class before evaluating its parents Avoids missing member in tangledCompanion.scala, which is a minimization of intermittent failures in CollectionStrawMan6. Intermittent, because it depended on order of compilation. CollectionTests have to be compiled together with but before CollectionStrawMan6 (this was _sometimes_ the case because partest did not honor indicated compilation order so far). I.e. dotc CollectionTests_2.scala CollectionStrawMan6_1.scala would trigger the error. tangledCompanion.scala captures the dependencies in a single file. --- src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/pos/tangledCompanion.scala | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/pos/tangledCompanion.scala diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 26c8f5c955b1..69ab35a7ccb9 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -698,13 +698,14 @@ class Namer { typer: Typer => // the parent types are elaborated. index(constr) symbolOfTree(constr).ensureCompleted() + + index(rest)(inClassContext(selfInfo)) val tparamAccessors = decls.filter(_ is TypeParamAccessor).toList val parentTypes = ensureFirstIsClass(parents.map(checkedParentType(_, tparamAccessors))) val parentRefs = ctx.normalizeToClassRefs(parentTypes, cls, decls) typr.println(s"completing $denot, parents = $parents, parentTypes = $parentTypes, parentRefs = $parentRefs") - index(rest)(inClassContext(selfInfo)) tempInfo.finalize(denot, parentRefs) Checking.checkWellFormed(cls) diff --git a/tests/pos/tangledCompanion.scala b/tests/pos/tangledCompanion.scala new file mode 100644 index 000000000000..5853f8675ec3 --- /dev/null +++ b/tests/pos/tangledCompanion.scala @@ -0,0 +1,26 @@ +object Test { + + import Coll._ + import LazyList.#:: + + val xs = LazyList.Empty + +} + +object Coll { + + trait IterableFactory[+C[X]] + + class LazyList[+A](expr: => LazyList.Evaluated[A]) + + 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] = ??? + } + } +} From dabf044485c60245e0fdf64c28eaa90285242a75 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jul 2016 20:43:58 +0200 Subject: [PATCH 14/17] Print kind of missing member When a member is missing, print whether we were looking for a type or a value. --- src/dotty/tools/dotc/typer/TypeAssigner.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/typer/TypeAssigner.scala b/src/dotty/tools/dotc/typer/TypeAssigner.scala index 1394d2e3ecfa..c2b7b7101d00 100644 --- a/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -203,12 +203,12 @@ trait TypeAssigner { TryDynamicCallType } else { if (!site.isErroneous) { + def notAMember = d"${if (name.isTypeName) "type" else "value"} $name is not a member of $site" ctx.error( if (name == nme.CONSTRUCTOR) d"$site does not have a constructor" - else if (site.derivesFrom(defn.DynamicClass)) { - d"$name is not a member of $site\n" + - "possible cause: maybe a wrong Dynamic method signature?" - } else d"$name is not a member of $site", pos) + else if (site.derivesFrom(defn.DynamicClass)) s"$notAMember\npossible cause: maybe a wrong Dynamic method signature?" + else notAMember, + pos) } ErrorType } From 8db8c110a2b2a287875222a74511511d80be2b15 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jul 2016 20:56:27 +0200 Subject: [PATCH 15/17] More systematic treatement of IndexedView Followinf @szeiger's suggestion, equip IndexView with optimized operations for map/drop/take. --- .../collections/CollectionStrawMan6.scala | 87 +++++++++++++------ .../run/colltest6/CollectionStrawMan6_1.scala | 87 +++++++++++++------ 2 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index 86ec660f37cf..654228c5b269 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -116,14 +116,17 @@ object CollectionStrawMan6 extends LowPriority { trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { /** The collection itself */ protected def coll: this.type = this - } + } - /** Base trait for sequence collections */ - trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { - def apply(i: Int): A + /** 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 @@ -156,6 +159,8 @@ object CollectionStrawMan6 extends LowPriority { } } + type IndexedSeq[+A] = Seq[A] { def view: IndexedView[A] } + /** Base trait for strict collections that can be built using a builder. * @param A the element type of the collection * @param Repr the type of the underlying collection @@ -593,6 +598,7 @@ object CollectionStrawMan6 extends LowPriority { } 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" } @@ -656,7 +662,8 @@ object CollectionStrawMan6 extends LowPriority { extends AnyVal with IterableOps[Char] with SeqMonoTransforms[Char, String] with IterablePolyTransforms[Char, List] - with Buildable[Char, String] { + with Buildable[Char, String] + with ArrayLike[Char] { protected def coll = new StringView(s) def iterator = coll.iterator @@ -671,6 +678,9 @@ object CollectionStrawMan6 extends LowPriority { 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" @@ -720,8 +730,7 @@ object CollectionStrawMan6 extends LowPriority { } case class StringView(s: String) extends IndexedView[Char] { - val start = 0 - val end = s.length + def length = s.length def apply(n: Int) = s.charAt(n) override def className = "StringView" } @@ -731,11 +740,15 @@ object CollectionStrawMan6 extends LowPriority { implicit class ArrayOps[A](val xs: Array[A]) extends AnyVal with IterableOps[A] with SeqMonoTransforms[A, Array[A]] - with Buildable[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) @@ -757,8 +770,7 @@ object CollectionStrawMan6 extends LowPriority { } case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { - val start = 0 - val end = xs.length + def length = xs.length def apply(n: Int) = xs(n) override def className = "ArrayView" } @@ -822,15 +834,17 @@ object CollectionStrawMan6 extends LowPriority { /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) + protected val normN = n max 0 override def knownSize = - if (underlying.knownSize >= 0) underlying.knownSize - n max 0 else -1 + if (underlying.knownSize >= 0) (underlying.knownSize - normN) max 0 else -1 } /** A view that takes leading elements of the underlying collection. */ case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.take(n) + protected val normN = n max 0 override def knownSize = - if (underlying.knownSize >= 0) underlying.knownSize min n else -1 + if (underlying.knownSize >= 0) underlying.knownSize min normN else -1 } /** A view that maps elements of the underlying collection. */ @@ -872,14 +886,11 @@ object CollectionStrawMan6 extends LowPriority { } /** View defined in terms of indexing a range */ - trait IndexedView[+A] extends View[A] { self => - def start: Int - def end: Int - def apply(i: Int): A + trait IndexedView[+A] extends View[A] with ArrayLike[A] { def iterator: Iterator[A] = new Iterator[A] { - private var current = start - def hasNext = current < end + private var current = 0 + def hasNext = current < length def next: A = { val r = apply(current) current += 1 @@ -887,14 +898,39 @@ object CollectionStrawMan6 extends LowPriority { } } - def reverse = new IndexedView[A] { - def start = self.start - def end = self.end - def apply(i: Int) = self.apply(end - 1 - i) + override def take(n: Int): IndexedView[A] = new IndexedView.Take(this, n) + override def drop(n: Int): IndexedView[A] = new IndexedView.Drop(this, n) + override def map[B](f: A => B): IndexedView[B] = new IndexedView.Map(this, f) + def reverse: IndexedView[A] = new IndexedView.Reverse(this) + } + + object IndexedView { + + class Take[A](underlying: IndexedView[A], n: Int) + extends View.Take(underlying, n) with IndexedView[A] { + override def iterator = super.iterator + def length = underlying.length min normN + def apply(i: Int) = underlying.apply(i) } - def length = end - start max 0 - override def knownSize = length + class Drop[A](underlying: IndexedView[A], n: Int) + extends View.Take(underlying, n) with IndexedView[A] { + override def iterator = super.iterator + def length = (underlying.length - normN) max 0 + def apply(i: Int) = underlying.apply(i + normN) + } + + class Map[A, B](underlying: IndexedView[A], f: A => B) + extends View.Map(underlying, f) with IndexedView[B] { + override def iterator = super.iterator + def length = underlying.length + def apply(n: Int) = f(underlying.apply(n)) + } + + case class Reverse[A](underlying: IndexedView[A]) extends IndexedView[A] { + def length = underlying.length + def apply(i: Int) = underlying.apply(length - 1 - i) + } } /* ---------- Iterators ---------------------------------------------------*/ @@ -1002,8 +1038,7 @@ object CollectionStrawMan6 extends LowPriority { def next = throw new NoSuchElementException("next on empty iterator") } def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { - val start = 0 - val end = xs.length + val length = xs.length def apply(n: Int) = xs(n) }.iterator } diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 5d3ea49913a6..40448e5dac90 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -117,14 +117,17 @@ object CollectionStrawMan6 extends LowPriority { trait Iterable[+A] extends IterableOnce[A] with IterableLike[A, Iterable] { /** The collection itself */ protected def coll: this.type = this - } + } - /** Base trait for sequence collections */ - trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] { - def apply(i: Int): A + /** 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 @@ -157,6 +160,8 @@ object CollectionStrawMan6 extends LowPriority { } } + type IndexedSeq[+A] = Seq[A] { def view: IndexedView[A] } + /** Base trait for strict collections that can be built using a builder. * @param A the element type of the collection * @param Repr the type of the underlying collection @@ -594,6 +599,7 @@ object CollectionStrawMan6 extends LowPriority { } 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" } @@ -657,7 +663,8 @@ object CollectionStrawMan6 extends LowPriority { extends AnyVal with IterableOps[Char] with SeqMonoTransforms[Char, String] with IterablePolyTransforms[Char, List] - with Buildable[Char, String] { + with Buildable[Char, String] + with ArrayLike[Char] { protected def coll = new StringView(s) def iterator = coll.iterator @@ -672,6 +679,9 @@ object CollectionStrawMan6 extends LowPriority { 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" @@ -721,8 +731,7 @@ object CollectionStrawMan6 extends LowPriority { } case class StringView(s: String) extends IndexedView[Char] { - val start = 0 - val end = s.length + def length = s.length def apply(n: Int) = s.charAt(n) override def className = "StringView" } @@ -732,11 +741,15 @@ object CollectionStrawMan6 extends LowPriority { implicit class ArrayOps[A](val xs: Array[A]) extends AnyVal with IterableOps[A] with SeqMonoTransforms[A, Array[A]] - with Buildable[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) @@ -758,8 +771,7 @@ object CollectionStrawMan6 extends LowPriority { } case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { - val start = 0 - val end = xs.length + def length = xs.length def apply(n: Int) = xs(n) override def className = "ArrayView" } @@ -823,15 +835,17 @@ object CollectionStrawMan6 extends LowPriority { /** A view that drops leading elements of the underlying collection. */ case class Drop[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.drop(n) + protected val normN = n max 0 override def knownSize = - if (underlying.knownSize >= 0) underlying.knownSize - n max 0 else -1 + if (underlying.knownSize >= 0) (underlying.knownSize - normN) max 0 else -1 } /** A view that takes leading elements of the underlying collection. */ case class Take[A](underlying: Iterable[A], n: Int) extends View[A] { def iterator = underlying.iterator.take(n) + protected val normN = n max 0 override def knownSize = - if (underlying.knownSize >= 0) underlying.knownSize min n else -1 + if (underlying.knownSize >= 0) underlying.knownSize min normN else -1 } /** A view that maps elements of the underlying collection. */ @@ -873,14 +887,11 @@ object CollectionStrawMan6 extends LowPriority { } /** View defined in terms of indexing a range */ - trait IndexedView[+A] extends View[A] { self => - def start: Int - def end: Int - def apply(i: Int): A + trait IndexedView[+A] extends View[A] with ArrayLike[A] { def iterator: Iterator[A] = new Iterator[A] { - private var current = start - def hasNext = current < end + private var current = 0 + def hasNext = current < length def next: A = { val r = apply(current) current += 1 @@ -888,14 +899,39 @@ object CollectionStrawMan6 extends LowPriority { } } - def reverse = new IndexedView[A] { - def start = self.start - def end = self.end - def apply(i: Int) = self.apply(end - 1 - i) + override def take(n: Int): IndexedView[A] = new IndexedView.Take(this, n) + override def drop(n: Int): IndexedView[A] = new IndexedView.Drop(this, n) + override def map[B](f: A => B): IndexedView[B] = new IndexedView.Map(this, f) + def reverse: IndexedView[A] = new IndexedView.Reverse(this) + } + + object IndexedView { + + class Take[A](underlying: IndexedView[A], n: Int) + extends View.Take(underlying, n) with IndexedView[A] { + override def iterator = super.iterator + def length = underlying.length min normN + def apply(i: Int) = underlying.apply(i) } - def length = end - start max 0 - override def knownSize = length + class Drop[A](underlying: IndexedView[A], n: Int) + extends View.Take(underlying, n) with IndexedView[A] { + override def iterator = super.iterator + def length = (underlying.length - normN) max 0 + def apply(i: Int) = underlying.apply(i + normN) + } + + class Map[A, B](underlying: IndexedView[A], f: A => B) + extends View.Map(underlying, f) with IndexedView[B] { + override def iterator = super.iterator + def length = underlying.length + def apply(n: Int) = f(underlying.apply(n)) + } + + case class Reverse[A](underlying: IndexedView[A]) extends IndexedView[A] { + def length = underlying.length + def apply(i: Int) = underlying.apply(length - 1 - i) + } } /* ---------- Iterators ---------------------------------------------------*/ @@ -1003,8 +1039,7 @@ object CollectionStrawMan6 extends LowPriority { def next = throw new NoSuchElementException("next on empty iterator") } def apply[A](xs: A*): Iterator[A] = new IndexedView[A] { - val start = 0 - val end = xs.length + val length = xs.length def apply(n: Int) = xs(n) }.iterator } From d88a280c56a7edb0a226820eba43dbbb9085e84e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jul 2016 10:17:59 +0200 Subject: [PATCH 16/17] Fix problem with IndexedView.iterator.length --- src/strawman/collections/CollectionStrawMan6.scala | 6 +++--- tests/run/colltest6/CollectionStrawMan6_1.scala | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/strawman/collections/CollectionStrawMan6.scala b/src/strawman/collections/CollectionStrawMan6.scala index 654228c5b269..50de63488698 100644 --- a/src/strawman/collections/CollectionStrawMan6.scala +++ b/src/strawman/collections/CollectionStrawMan6.scala @@ -886,11 +886,11 @@ object CollectionStrawMan6 extends LowPriority { } /** View defined in terms of indexing a range */ - trait IndexedView[+A] extends View[A] with ArrayLike[A] { + trait IndexedView[+A] extends View[A] with ArrayLike[A] { self => def iterator: Iterator[A] = new Iterator[A] { private var current = 0 - def hasNext = current < length + def hasNext = current < self.length def next: A = { val r = apply(current) current += 1 @@ -908,7 +908,7 @@ object CollectionStrawMan6 extends LowPriority { class Take[A](underlying: IndexedView[A], n: Int) extends View.Take(underlying, n) with IndexedView[A] { - override def iterator = super.iterator + override def iterator = super.iterator // needed to avoid "conflicting overrides" error def length = underlying.length min normN def apply(i: Int) = underlying.apply(i) } diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 40448e5dac90..50de63488698 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -1,4 +1,3 @@ -package colltest6 package strawman.collections import Predef.{augmentString => _, wrapString => _, _} @@ -887,11 +886,11 @@ object CollectionStrawMan6 extends LowPriority { } /** View defined in terms of indexing a range */ - trait IndexedView[+A] extends View[A] with ArrayLike[A] { + trait IndexedView[+A] extends View[A] with ArrayLike[A] { self => def iterator: Iterator[A] = new Iterator[A] { private var current = 0 - def hasNext = current < length + def hasNext = current < self.length def next: A = { val r = apply(current) current += 1 @@ -909,7 +908,7 @@ object CollectionStrawMan6 extends LowPriority { class Take[A](underlying: IndexedView[A], n: Int) extends View.Take(underlying, n) with IndexedView[A] { - override def iterator = super.iterator + override def iterator = super.iterator // needed to avoid "conflicting overrides" error def length = underlying.length min normN def apply(i: Int) = underlying.apply(i) } From 4e95a105869ec2d978d5e7e0a3f78442e19b2fe5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jul 2016 12:18:07 +0200 Subject: [PATCH 17/17] Add missing import --- tests/run/colltest6/CollectionStrawMan6_1.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/run/colltest6/CollectionStrawMan6_1.scala b/tests/run/colltest6/CollectionStrawMan6_1.scala index 50de63488698..8400fc05f6e5 100644 --- a/tests/run/colltest6/CollectionStrawMan6_1.scala +++ b/tests/run/colltest6/CollectionStrawMan6_1.scala @@ -1,3 +1,4 @@ +package colltest6 package strawman.collections import Predef.{augmentString => _, wrapString => _, _}