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

Commit

Permalink
Add non-empty collections
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrf committed Dec 27, 2016
1 parent 8f5f03c commit 094f5bf
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 54 deletions.
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
69 changes: 49 additions & 20 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,25 @@ 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 Some((head, tail)) = current.uncons
current = tail
head
}
}

/** `length` is defined in terms of `iterator` */
Expand All @@ -36,11 +39,23 @@ trait LinearSeq[+A] extends Seq[A] with LinearSeqLike[A, LinearSeq] { self =>
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
skipped.uncons.fold(throw new IndexOutOfBoundsException(n.toString))(_._1)
}
}

/**
* 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 +69,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])]

/** 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] = {
// 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.
if (n <= 0) s else {
s.uncons match {
case None => s
case Some((_, t)) => loop(n - 1, t.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
33 changes: 22 additions & 11 deletions src/main/scala/strawman/collection/immutable/List.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package strawman.collection.immutable

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


Expand All @@ -18,12 +18,14 @@ sealed trait List[+A]
protected[this] def newBuilder = new ListBuffer[A].mapResult(_.toList)

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

/** Prepend operation that avoids copying this list */
def ++:[B >: A](prefix: List[B]): List[B] =
if (prefix.isEmpty) this
else prefix.head :: prefix.tail ++: this
prefix match {
case Nil => this
case h :: t => h :: (t ++: 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 +37,30 @@ 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))
}

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

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

0 comments on commit 094f5bf

Please sign in to comment.