From 6de2bf8f9d78df9db95fac1e49dec38105176749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 29 Apr 2024 19:06:07 -0500 Subject: [PATCH 1/8] Override LazyList.range --- src/library/scala/collection/immutable/LazyList.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index b6af234150e2..0653ffe169ae 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -968,6 +968,13 @@ final class LazyList[+A] private(private[this] var lazyState: () => LazyList.Sta */ @SerialVersionUID(3L) object LazyList extends SeqFactory[LazyList] { + // Override SeqFactory methods. + override def range[A: Integral](start: A, end: A): LazyList[A] = + range(start, end, step = Integral[A].one) + + override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = + LazyList.iterate(start)(ev.plus(_, step)).takeWhile(ev.lt(_, end)) + // Eagerly evaluate cached empty instance private[this] val _empty = newLL(State.Empty).force From 91088b59a3f3d7baf6a87958ccfa4f0ef6c2cf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 29 Apr 2024 19:22:55 -0500 Subject: [PATCH 2/8] Test LazyList.range --- .../scala/collection/immutable/LazyListTest.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/junit/scala/collection/immutable/LazyListTest.scala b/test/junit/scala/collection/immutable/LazyListTest.scala index 8ff9ebb72361..5e8c257f7145 100644 --- a/test/junit/scala/collection/immutable/LazyListTest.scala +++ b/test/junit/scala/collection/immutable/LazyListTest.scala @@ -444,8 +444,20 @@ class LazyListTest { LazyList(1).lazyAppendedAll({ count += 1; Seq(2)}).toList assertEquals(1, count) } + + @Test + def lazyRangeAllowsMoreThanIntMaxValue(): Unit = { + val totalElements: Long = Int.MaxValue.toLong + 2L + val count: Long = + LazyList + .range(start = 0L, end = totalElements) + .foldLeft(0L) { case (acc, _) => + acc + 1L + } + assertEquals(totalElements, count) + } } object LazyListTest { var serializationForceCount = 0 -} \ No newline at end of file +} From bd3db0d40a434241d674e5ea0d23ab6f19f5248f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 11:32:19 -0500 Subject: [PATCH 3/8] Better LazyList.range impl Co-authored-by: Marissa | April <7505383+NthPortal@users.noreply.github.com> --- src/library/scala/collection/immutable/LazyList.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index 0653ffe169ae..db7db6a83cbb 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -973,7 +973,11 @@ object LazyList extends SeqFactory[LazyList] { range(start, end, step = Integral[A].one) override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = - LazyList.iterate(start)(ev.plus(_, step)).takeWhile(ev.lt(_, end)) + newLL(rangeImpl(start, end, step)) + + private[this] def rangeImpl[A](start: A, end: A, step: A)(implicit ev: Integral[A]): State[A] = + if (ev.lt(start, end)) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step))) + else State.Empty // Eagerly evaluate cached empty instance private[this] val _empty = newLL(State.Empty).force From f7745a48f0f71c71ed9558e9ed32164aec30fc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 11:34:04 -0500 Subject: [PATCH 4/8] Move LazyList.range implementation closer to other overrides --- .../scala/collection/immutable/LazyList.scala | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index db7db6a83cbb..952c82b3f2e0 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -968,17 +968,6 @@ final class LazyList[+A] private(private[this] var lazyState: () => LazyList.Sta */ @SerialVersionUID(3L) object LazyList extends SeqFactory[LazyList] { - // Override SeqFactory methods. - override def range[A: Integral](start: A, end: A): LazyList[A] = - range(start, end, step = Integral[A].one) - - override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = - newLL(rangeImpl(start, end, step)) - - private[this] def rangeImpl[A](start: A, end: A, step: A)(implicit ev: Integral[A]): State[A] = - if (ev.lt(start, end)) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step))) - else State.Empty - // Eagerly evaluate cached empty instance private[this] val _empty = newLL(State.Empty).force @@ -1241,6 +1230,16 @@ object LazyList extends SeqFactory[LazyList] { at(0) } + override def range[A: Integral](start: A, end: A): LazyList[A] = + range(start, end, step = Integral[A].one) + + override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = + newLL(rangeImpl(start, end, step)) + + private[this] def rangeImpl[A](start: A, end: A, step: A)(implicit ev: Integral[A]): State[A] = + if (ev.lt(start, end)) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step))) + else State.Empty + // significantly simpler than the iterator returned by Iterator.unfold override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyList[A] = newLL { From 2028daea112f068fe8839179aef1cfb6f16d94ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 14:44:25 -0500 Subject: [PATCH 5/8] Ensure lazyness of `Integral[A].one` for overrided `range` implementation Co-authored-by: Marissa | April <7505383+NthPortal@users.noreply.github.com> --- src/library/scala/collection/immutable/LazyList.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index 952c82b3f2e0..d0daa842e261 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -1231,7 +1231,7 @@ object LazyList extends SeqFactory[LazyList] { } override def range[A: Integral](start: A, end: A): LazyList[A] = - range(start, end, step = Integral[A].one) + newLL(rangeImpl(start, end, step = Integral[A].one)) override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = newLL(rangeImpl(start, end, step)) From fbc26235526f1bd8f6047137654cb8fecab5a8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 14:45:02 -0500 Subject: [PATCH 6/8] Ensure step can't be zero Co-authored-by: Marissa | April <7505383+NthPortal@users.noreply.github.com> --- src/library/scala/collection/immutable/LazyList.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index d0daa842e261..ae5d93073c41 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -1234,7 +1234,10 @@ object LazyList extends SeqFactory[LazyList] { newLL(rangeImpl(start, end, step = Integral[A].one)) override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = - newLL(rangeImpl(start, end, step)) + newLL { + if (ev.equiv(step, ev.zero)) throw new IllegalArgumentException("step cannot be 0.") + else rangeImpl(start, end, step) + } private[this] def rangeImpl[A](start: A, end: A, step: A)(implicit ev: Integral[A]): State[A] = if (ev.lt(start, end)) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step))) From 1ac4f643c3f27d3d6b6b322d2afeccf4fb691b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 17:45:04 -0500 Subject: [PATCH 7/8] Allow reverse LazyList.range --- src/library/scala/collection/immutable/LazyList.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/library/scala/collection/immutable/LazyList.scala b/src/library/scala/collection/immutable/LazyList.scala index ae5d93073c41..db203126414a 100644 --- a/src/library/scala/collection/immutable/LazyList.scala +++ b/src/library/scala/collection/immutable/LazyList.scala @@ -1231,17 +1231,19 @@ object LazyList extends SeqFactory[LazyList] { } override def range[A: Integral](start: A, end: A): LazyList[A] = - newLL(rangeImpl(start, end, step = Integral[A].one)) + newLL(rangeImpl(start, end, step = Integral[A].one, sign = true)) override def range[A](start: A, end: A, step: A)(implicit ev: Integral[A]): LazyList[A] = newLL { if (ev.equiv(step, ev.zero)) throw new IllegalArgumentException("step cannot be 0.") - else rangeImpl(start, end, step) + else rangeImpl(start, end, step, sign = ev.sign(step) == ev.one) } - private[this] def rangeImpl[A](start: A, end: A, step: A)(implicit ev: Integral[A]): State[A] = - if (ev.lt(start, end)) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step))) + private[this] def rangeImpl[A](start: A, end: A, step: A, sign: Boolean)(implicit ev: Integral[A]): State[A] = { + val check = if (sign) ev.lt(start, end) else ev.gt(start, end) + if (check) sCons(start, newLL(rangeImpl(ev.plus(start, step), end, step, sign))) else State.Empty + } // significantly simpler than the iterator returned by Iterator.unfold override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyList[A] = From b4eb90ac556cb34db6799ef596b0ef02e58d7be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 30 Apr 2024 17:46:25 -0500 Subject: [PATCH 8/8] Make the lazyRangeAllowsMoreThanIntMaxValue test run fast --- .../collection/immutable/LazyListTest.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/junit/scala/collection/immutable/LazyListTest.scala b/test/junit/scala/collection/immutable/LazyListTest.scala index 5e8c257f7145..660e1cd6d853 100644 --- a/test/junit/scala/collection/immutable/LazyListTest.scala +++ b/test/junit/scala/collection/immutable/LazyListTest.scala @@ -447,14 +447,15 @@ class LazyListTest { @Test def lazyRangeAllowsMoreThanIntMaxValue(): Unit = { - val totalElements: Long = Int.MaxValue.toLong + 2L - val count: Long = - LazyList - .range(start = 0L, end = totalElements) - .foldLeft(0L) { case (acc, _) => - acc + 1L - } - assertEquals(totalElements, count) + // We use a reverse range to avoid computing a lot of elements. + val maxValue = Int.MaxValue.toLong + val lazyList = LazyList.range( + start = maxValue * 2L, + end = 0, + step = -1L + ) + val nums = lazyList.take(10) + assert(nums.forall(_ > maxValue)) } }