Skip to content

Commit 0c4859c

Browse files
authored
Merge pull request scala#7573 from NthPortal/topic/ll-lazy-builder/PR
Add lazy builder for LazyList
2 parents d64a789 + 28e5b22 commit 0c4859c

File tree

4 files changed

+139
-47
lines changed

4 files changed

+139
-47
lines changed

src/library/scala/collection/immutable/LazyList.scala

Lines changed: 112 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import java.lang.{StringBuilder => JStringBuilder}
1919

2020
import scala.annotation.tailrec
2121
import scala.collection.generic.SerializeEnd
22-
import scala.collection.mutable.{ArrayBuffer, Builder, StringBuilder}
22+
import scala.collection.mutable.{ArrayBuffer, Builder, ReusableBuilder, StringBuilder}
2323
import scala.language.implicitConversions
2424

2525
/** The class `LazyList` implements lazy lists where elements
@@ -194,6 +194,10 @@ import scala.language.implicitConversions
194194
* @define coll lazy list
195195
* @define orderDependent
196196
* @define orderDependentFold
197+
* @define appendStackSafety Note: Repeated chaining of calls to append methods (`appended`,
198+
* `appendedAll`, `lazyAppendedAll`) without forcing any of the
199+
* intermediate resulting lazy lists may overflow the stack when
200+
* the final result is forced.
197201
*/
198202
@SerialVersionUID(3L)
199203
final class LazyList[+A] private(private[this] var lazyState: () => LazyList.State[A])
@@ -300,6 +304,8 @@ final class LazyList[+A] private(private[this] var lazyState: () => LazyList.Sta
300304
override protected[this] def className = "LazyList"
301305

302306
/** The lazy list resulting from the concatenation of this lazy list with the argument lazy list.
307+
*
308+
* $appendStackSafety
303309
*
304310
* @param suffix The collection that gets appended to this lazy list
305311
* @return The lazy list containing elements of this lazy list and the iterable object.
@@ -314,10 +320,18 @@ final class LazyList[+A] private(private[this] var lazyState: () => LazyList.Sta
314320
else sCons(head, tail lazyAppendedAll suffix)
315321
}
316322

323+
/** @inheritdoc
324+
*
325+
* $appendStackSafety
326+
*/
317327
override def appendedAll[B >: A](suffix: IterableOnce[B]): LazyList[B] =
318328
if (knownIsEmpty) LazyList.from(suffix)
319329
else lazyAppendedAll(suffix)
320330

331+
/** @inheritdoc
332+
*
333+
* $appendStackSafety
334+
*/
321335
override def appended[B >: A](elem: B): LazyList[B] =
322336
if (knownIsEmpty) newLL(sCons(elem, LazyList.empty))
323337
else lazyAppendedAll(Iterator.single(elem))
@@ -789,38 +803,6 @@ object LazyList extends SeqFactory[LazyList] {
789803
final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
790804
}
791805

792-
private class SlidingIterator[A](private[this] var lazyList: LazyList[A], size: Int, step: Int)
793-
extends AbstractIterator[LazyList[A]] {
794-
private val minLen = size - step max 0
795-
private var first = true
796-
797-
def hasNext: Boolean =
798-
if (first) lazyList.nonEmpty
799-
else lazyList.lengthGt(minLen)
800-
801-
def next(): LazyList[A] = {
802-
if (!hasNext) Iterator.empty.next()
803-
else {
804-
first = false
805-
val list = lazyList
806-
lazyList = list.drop(step)
807-
list.take(size)
808-
}
809-
}
810-
}
811-
812-
private class LazyIterator[+A](private[this] var lazyList: LazyList[A]) extends AbstractIterator[A] {
813-
override def hasNext: Boolean = lazyList.nonEmpty
814-
815-
override def next(): A =
816-
if (lazyList.isEmpty) Iterator.empty.next()
817-
else {
818-
val res = lazyList.head
819-
lazyList = lazyList.tail
820-
res
821-
}
822-
}
823-
824806
/** Creates a new LazyList. */
825807
@inline private def newLL[A](state: => State[A]): LazyList[A] = new LazyList[A](() => state)
826808

@@ -886,15 +868,6 @@ object LazyList extends SeqFactory[LazyList] {
886868
if (!it.hasNext) State.Empty
887869
else stateFromIteratorConcatSuffix(it.next().iterator)(concatIterator(it))
888870

889-
private final class WithFilter[A] private[LazyList](lazyList: LazyList[A], p: A => Boolean)
890-
extends collection.WithFilter[A, LazyList] {
891-
private[this] val filtered = lazyList.filter(p)
892-
def map[B](f: A => B): LazyList[B] = filtered.map(f)
893-
def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = filtered.flatMap(f)
894-
def foreach[U](f: A => U): Unit = filtered.foreach(f)
895-
def withFilter(q: A => Boolean): collection.WithFilter[A, LazyList] = new WithFilter(filtered, q)
896-
}
897-
898871
/** An infinite LazyList that repeatedly applies a given function to a start value.
899872
*
900873
* @param start the start value of the LazyList
@@ -954,7 +927,103 @@ object LazyList extends SeqFactory[LazyList] {
954927
}
955928
}
956929

957-
def newBuilder[A]: Builder[A, LazyList[A]] = ArrayBuffer.newBuilder[A].mapResult(array => from(array))
930+
def newBuilder[A]: Builder[A, LazyList[A]] = new LazyBuilder[A]
931+
932+
private class LazyIterator[+A](private[this] var lazyList: LazyList[A]) extends AbstractIterator[A] {
933+
override def hasNext: Boolean = lazyList.nonEmpty
934+
935+
override def next(): A =
936+
if (lazyList.isEmpty) Iterator.empty.next()
937+
else {
938+
val res = lazyList.head
939+
lazyList = lazyList.tail
940+
res
941+
}
942+
}
943+
944+
private class SlidingIterator[A](private[this] var lazyList: LazyList[A], size: Int, step: Int)
945+
extends AbstractIterator[LazyList[A]] {
946+
private val minLen = size - step max 0
947+
private var first = true
948+
949+
def hasNext: Boolean =
950+
if (first) lazyList.nonEmpty
951+
else lazyList.lengthGt(minLen)
952+
953+
def next(): LazyList[A] = {
954+
if (!hasNext) Iterator.empty.next()
955+
else {
956+
first = false
957+
val list = lazyList
958+
lazyList = list.drop(step)
959+
list.take(size)
960+
}
961+
}
962+
}
963+
964+
private final class WithFilter[A] private[LazyList](lazyList: LazyList[A], p: A => Boolean)
965+
extends collection.WithFilter[A, LazyList] {
966+
private[this] val filtered = lazyList.filter(p)
967+
def map[B](f: A => B): LazyList[B] = filtered.map(f)
968+
def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = filtered.flatMap(f)
969+
def foreach[U](f: A => U): Unit = filtered.foreach(f)
970+
def withFilter(q: A => Boolean): collection.WithFilter[A, LazyList] = new WithFilter(filtered, q)
971+
}
972+
973+
private final class LazyBuilder[A] extends ReusableBuilder[A, LazyList[A]] {
974+
import LazyBuilder._
975+
976+
private[this] var next: DeferredState[A] = _
977+
private[this] var list: LazyList[A] = _
978+
979+
clear()
980+
981+
override def clear(): Unit = {
982+
val deferred = new DeferredState[A]
983+
list = newLL(deferred.eval())
984+
next = deferred
985+
}
986+
987+
override def result(): LazyList[A] = {
988+
next init State.Empty
989+
list
990+
}
991+
992+
override def addOne(elem: A): this.type = {
993+
val deferred = new DeferredState[A]
994+
next init sCons(elem, newLL(deferred.eval()))
995+
next = deferred
996+
this
997+
}
998+
999+
// lazy implementation which doesn't evaluate the collection being added
1000+
override def addAll(xs: IterableOnce[A]): this.type = {
1001+
if (xs.knownSize != 0) {
1002+
val deferred = new DeferredState[A]
1003+
next init stateFromIteratorConcatSuffix(xs.iterator)(deferred.eval())
1004+
next = deferred
1005+
}
1006+
this
1007+
}
1008+
}
1009+
1010+
private object LazyBuilder {
1011+
final class DeferredState[A] {
1012+
private[this] var _state: () => State[A] = _
1013+
1014+
def eval(): State[A] = {
1015+
val state = _state
1016+
if (state == null) throw new IllegalStateException("uninitialized")
1017+
state()
1018+
}
1019+
1020+
// racy
1021+
def init(state: => State[A]): Unit = {
1022+
if (_state != null) throw new IllegalStateException("already initialized")
1023+
_state = () => state
1024+
}
1025+
}
1026+
}
9581027

9591028
/** This serialization proxy is used for LazyLists which start with a sequence of evaluated cons cells.
9601029
* The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses

test/junit/scala/collection/immutable/LazyListLazinessTest.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,13 @@ class LazyListLazinessTest {
881881
def unapplySeq_properlyLazy(): Unit = {
882882
genericLazyOp_properlyLazy(LazyList.unapplySeq(_).toSeq.to(LazyList))
883883
}
884+
885+
@Test
886+
def builder_properlyLazy(): Unit = {
887+
val op = lazyListOp { ll => (LazyList.newBuilder[Int] ++= ll).result() }
888+
assertLazyAll(op)
889+
assertRepeatedlyLazy(op)
890+
}
884891
}
885892

886893
private object LazyListLazinessTest {

test/junit/scala/collection/immutable/LazyListTest.scala

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package scala.collection.immutable
1+
package scala.collection
2+
package immutable
23

34
import org.junit.runner.RunWith
45
import org.junit.runners.JUnit4
56
import org.junit.Test
67
import org.junit.Assert._
78

8-
import scala.collection.Iterator
9-
import scala.collection.mutable.ListBuffer
9+
import scala.collection.mutable.{Builder, ListBuffer}
1010
import scala.ref.WeakReference
1111
import scala.tools.testing.AssertUtil
1212
import scala.util.Try
@@ -383,4 +383,21 @@ class LazyListTest {
383383
check(LazyList.from(Vector(1, 2, 3, 4, 5)))
384384
check(LazyList.tabulate(5)(_ + 1))
385385
}
386+
387+
@Test
388+
def builder(): Unit = {
389+
def build(init: Builder[Int, LazyList[Int]] => Unit): LazyList[Int] = {
390+
val b = LazyList.newBuilder[Int]
391+
init(b)
392+
b.result()
393+
}
394+
395+
assertEquals(Nil, build(_ => ()))
396+
assertEquals(Nil, build(_ ++= Nil))
397+
assertEquals(Nil, build(_ ++= LazyList.empty))
398+
assertEquals(1 to 10, build(_ += 1 ++= (2 to 5) += 6 += 7 ++= (8 to 10)))
399+
assertEquals(1 to 10, build(_ ++= (1 to 4) ++= (5 to 6) += 7 ++= (8 to 9) += 10))
400+
assertEquals(1 to 10, build(_ ++= LazyList.from(1).take(10)))
401+
assertEquals(1 to 10, build(_ ++= Iterator.from(1).take(10)))
402+
}
386403
}

test/scalacheck/scala/collection/immutable/SeqProperties.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ object SeqProperties extends Properties("immutable.Seq builder implementations")
1818
property("Seq builder stateful testing") = new SeqBuilderStateProperties(Seq.newBuilder[A]).property()
1919
property("List builder stateful testing") = new SeqBuilderStateProperties(List.newBuilder[A]).property()
2020
property("ArraySeq builder stateful testing") = new SeqBuilderStateProperties(ArraySeq.newBuilder[A]).property()
21-
property("LazyList builder stateful testing") = new SeqBuilderStateProperties(LazyList.newBuilder[A]).property()
2221
property("Queue builder stateful testing") = new SeqBuilderStateProperties(Queue.newBuilder[A]).property()
2322
property("IndexedSeq builder stateful testing") = new SeqBuilderStateProperties(IndexedSeq.newBuilder[A]).property()
2423
property("Stream builder stateful testing") = new SeqBuilderStateProperties(Stream.newBuilder[A]).property()

0 commit comments

Comments
 (0)