Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.

Add statically known non-empty collections #9

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/main/scala/strawman/collection/Iterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package strawman.collection

import scala.annotation.unchecked.uncheckedVariance
import scala.reflect.ClassTag
import scala.{Int, Boolean, Array, Any, Unit, StringContext}
import scala.{Int, Boolean, Array, Any, Unit, StringContext, Option}
import java.lang.String

import strawman.collection.mutable.{ArrayBuffer, StringBuilder}
Expand Down Expand Up @@ -69,8 +69,7 @@ trait IterableOps[+A] extends Any {
/** Is the collection empty? */
def isEmpty: Boolean = !iterator().hasNext

/** The first element of the collection. */
def head: A = iterator().next()
def headOption: Option[A] = iterator().headOption

/** The number of elements in this collection, if it can be cheaply computed,
* -1 otherwise. Cheaply usually means: Not requiring a collection traversal.
Expand Down Expand Up @@ -165,8 +164,6 @@ trait IterableMonoTransforms[+A, +Repr] extends Any {
*/
def drop(n: Int): Repr = fromIterableWithSameElemType(View.Drop(coll, n))

/** The rest of the collection without its first element. */
def tail: Repr = drop(1)
}

/** Transforms over iterables that can return collections of different element types.
Expand Down
64 changes: 45 additions & 19 deletions src/main/scala/strawman/collection/Seq.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package strawman.collection

import scala.{Any, Boolean, Int, IndexOutOfBoundsException}
import scala.{Any, Boolean, Int, IndexOutOfBoundsException, Option, Some, None}
import strawman.collection.mutable.Iterator
import strawman.collection.immutable.{List, Nil}

Expand All @@ -9,22 +9,21 @@ import scala.annotation.unchecked.uncheckedVariance
/** Base trait for sequence collections */
trait Seq[+A] extends Iterable[A] with SeqLike[A, Seq] with ArrayLike[A]

trait InhabitedSeq[+A]
extends Seq[A]
with InhabitedLinearSeqOps[A, Seq[A]]

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

/** To be overridden in implementations: */
def isEmpty: Boolean
def head: A
def tail: LinearSeq[A]

/** `iterator` is overridden in terms of `head` and `tail` */
def iterator() = new Iterator[A] {
private[this] var current: Seq[A] = self
private[this] var current: LinearSeq[A] = self
def hasNext = !current.isEmpty
def next() = { val r = current.head; current = current.tail; r }
def next() = { val r = current.unsafeHead; current = current.unsafeTail; r }
}

/** `length` is defined in terms of `iterator` */
Expand All @@ -37,10 +36,23 @@ trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self =>
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
val skipped = drop(n)
if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString)
skipped.head
skipped.unsafeHead
}
}

/**
* Collections that have at least one `head` element followed
* by a `tail`.
*/
trait InhabitedLinearSeqOps[+A, +Repr] extends Any {

/** The first element of the collection. */
def head: A

/** The rest of the collection without its first element. */
def tail: Repr
}

trait IndexedSeq[+A] extends Seq[A] { self =>
override def view: IndexedView[A] = new IndexedView[A] {
def length: Int = self.length
Expand All @@ -54,24 +66,38 @@ trait SeqLike[+A, +C[X] <: Seq[X]]
extends IterableLike[A, C]
with SeqMonoTransforms[A, C[A @uncheckedVariance]] // sound bcs of VarianceNote

trait InhabitedLinearSeqFactory[+C[X] <: InhabitedSeq[X]] {
def apply[A](a: A, as: A*): C[A]
}

/** Base trait for linear Seq operations */
trait LinearSeqLike[+A, +C[X] <: LinearSeq[X]] extends SeqLike[A, C] {
trait LinearSeqLike[+A, +C[+X] <: LinearSeq[X]] extends SeqLike[A, C] {

protected def coll: C[A]

/** Extract the head and tail, if the collection is not empty */
def uncons: Option[(A, C[A])]

/** To be overriden for performance in subclasses */
protected def unsafeHead: A = uncons.get._1
protected def unsafeTail: C[A] = uncons.get._2

/** Optimized version of `drop` that avoids copying
* Note: `drop` is defined here, rather than in a trait like `LinearSeqMonoTransforms`,
* 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)
override def drop(n: Int): C[A] = {
def loop(n: Int, s: C[A]): C[A] = {
if (n <= 0) s
// implicit contract to guarantee success of asInstanceOf:
// (1) coll is of type C[A]
// (2) The tail of a LinearSeq is of the same type as the type of the sequence itself
// it's surprisingly tricky/ugly to turn this into actual types, so we
// leave this contract implicit.
else loop(n - 1, s.unsafeTail.asInstanceOf[C[A]])
}
loop(n, coll)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/strawman/collection/immutable/LazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ class LazyList[+A](expr: => LazyList.Evaluated[A])
}

override def isEmpty = force.isEmpty
override def head = force.get._1
override def tail = force.get._2

def #:: [B >: A](elem: => B): LazyList[B] = new LazyList(Some((elem, this)))

Expand All @@ -34,6 +32,8 @@ class LazyList[+A](expr: => LazyList.Evaluated[A])
case Some((hd, tl)) => s"$hd #:: $tl"
}
else "LazyList(?)"

def uncons: Option[(A, LazyList[A])] = force
}

object LazyList extends IterableFactory[LazyList] {
Expand Down
38 changes: 27 additions & 11 deletions src/main/scala/strawman/collection/immutable/List.scala
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package strawman.collection.immutable

import scala.annotation.unchecked.uncheckedVariance
import scala.Nothing
import scala.{Option, None, NoSuchElementException, Nothing, Some, UnsupportedOperationException}
import scala.Predef.???
import strawman.collection.{Iterable, IterableFactory, IterableOnce, LinearSeq, SeqLike}
import strawman.collection.{InhabitedLinearSeqFactory, InhabitedLinearSeqOps, InhabitedSeq, Iterable, IterableFactory, IterableOnce, LinearSeq, LinearSeqLike, View}
import strawman.collection.mutable.{Buildable, ListBuffer}


/** Concrete collection type: List */
sealed trait List[+A]
extends LinearSeq[A]
with SeqLike[A, List]
with LinearSeqLike[A, List]
with Buildable[A, List[A]] {

def fromIterable[B](c: Iterable[B]): List[B] = List.fromIterable(c)

protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList)

/** Prepend element */
def :: [B >: A](elem: B): List[B] = new ::(elem, this)
def :: [B >: A](elem: B): List.NonEmpty[B] = new ::(elem, this)

/** Prepend operation that avoids copying this list */
def ++:[B >: A](prefix: List[B]): List[B] =
if (prefix.isEmpty) this
else prefix.head :: prefix.tail ++: this
else prefix.unsafeHead :: (prefix.unsafeTail ++: this)

/** When concatenating with another list `xs`, avoid copying `xs` */
override def ++[B >: A](xs: IterableOnce[B]): List[B] = xs match {
Expand All @@ -35,21 +35,37 @@ sealed trait List[+A]
}

case class :: [+A](x: A, private[collection] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally
extends List[A] {
extends List[A]
with InhabitedSeq[A]
with InhabitedLinearSeqOps[A, List[A]] {

override def isEmpty = false
override def head = x
override def tail = next
def head = x
def tail = next

def uncons: Option[(A, List[A])] = Some((x, next))
override protected def unsafeHead: A = x
override protected def unsafeTail: List[A] = next
}

case object Nil extends List[Nothing] {
override def isEmpty = true
override def head = ???
override def tail = ???

def uncons = None
override protected def unsafeHead: Nothing = throw new NoSuchElementException
override protected def unsafeTail: Nothing = throw new UnsupportedOperationException
}

object List extends IterableFactory[List] {
object List extends IterableFactory[List] with InhabitedLinearSeqFactory[::] {

type NonEmpty[A] = ::[A]

def fromIterable[B](coll: Iterable[B]): List[B] = coll match {
case coll: List[B] => coll
case _ => ListBuffer.fromIterable(coll).toList
}

def apply[A](a: A, as: A*): List.NonEmpty[A] =
a :: fromIterable(View.Elems(as: _*))

}
5 changes: 4 additions & 1 deletion src/main/scala/strawman/collection/mutable/Iterator.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package strawman.collection.mutable

import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException}
import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException, Option, Some, None}
import strawman.collection.{IndexedView, IterableOnce}

/** A core Iterator class */
Expand All @@ -26,6 +26,9 @@ trait Iterator[+A] { self =>
while (hasNext) { len += 1; next() }
len
}

def headOption: Option[A] = if (hasNext) Some(next()) else None

def filter(p: A => Boolean): Iterator[A] = new Iterator[A] {
private var hd: A = _
private var hdDefined: Boolean = false
Expand Down
18 changes: 3 additions & 15 deletions src/test/scala/strawman/collection/test/Test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.junit.Test

class StrawmanTest {

def seqOps(xs: Seq[Int]): Unit = {
def seqOps(xs: InhabitedSeq[Int]): Unit = {
val x1 = xs.foldLeft("")(_ + _)
val y1: String = x1
val x2 = xs.foldRight("")(_ + _)
Expand Down Expand Up @@ -69,8 +69,6 @@ class StrawmanTest {
val y2: String = x2
val x3 = xs.indexWhere(_ % 2 == 0)
val y3: Int = x3
val x4 = xs.head
val y4: Int = x4
val x5 = xs.to(List)
val y5: List[Int] = x5
val (xs6, xs7) = xs.partition(_ % 2 == 0)
Expand All @@ -96,7 +94,6 @@ class StrawmanTest {
println(x1)
println(x2)
println(x3)
println(x4)
println(x5)
println(xs6.to(List))
println(xs7.to(List))
Expand All @@ -117,8 +114,6 @@ class StrawmanTest {
val y2: String = x2
val x3 = xs.indexWhere(_ % 2 == 0)
val y3: Int = x3
val x4 = xs.head
val y4: Int = x4
val x5 = xs.to(List)
val y5: List[Char] = x5
val (xs6, xs7) = xs.partition(_ % 2 == 0)
Expand Down Expand Up @@ -148,7 +143,6 @@ class StrawmanTest {
println(x1)
println(x2)
println(x3)
println(x4)
println(x5)
println(xs6)
println(xs7)
Expand All @@ -171,8 +165,6 @@ class StrawmanTest {
val y2: String = x2
val x3 = xs.indexWhere(_ % 2 == 0)
val y3: Int = x3
val x4 = xs.head
val y4: Int = x4
val x5 = xs.to(List)
val y5: List[Int] = x5
val (xs6, xs7) = xs.partition(_ % 2 == 0)
Expand Down Expand Up @@ -200,7 +192,6 @@ class StrawmanTest {
println(x1)
println(x2)
println(x3)
println(x4)
println(x5)
println(xs6.view)
println(xs7.view)
Expand All @@ -222,8 +213,6 @@ class StrawmanTest {
val y2: String = x2
val x3 = xs.indexWhere(_ % 2 == 0)
val y3: Int = x3
val x4 = xs.head
val y4: Int = x4
val x5 = xs.to(List)
val y5: List[Int] = x5
val (xs6, xs7) = xs.partition(_ % 2 == 0)
Expand Down Expand Up @@ -251,7 +240,6 @@ class StrawmanTest {
println(x1)
println(x2)
println(x3)
println(x4)
println(x5)
println(xs6)
println(xs6.to(List))
Expand Down Expand Up @@ -295,8 +283,8 @@ class StrawmanTest {
val intsListBuf = ints.to(ListBuffer)
val intsView = ints.view
seqOps(ints)
seqOps(intsBuf)
seqOps(intsListBuf)
// seqOps(intsBuf)
// seqOps(intsListBuf)
viewOps(intsView)
stringOps("abc")
arrayOps(Array(1, 2, 3))
Expand Down