@@ -43,29 +43,29 @@ object LazyEvaluation extends ScalaTutorialSection {
4343 * - Avoid computing the tail of a sequence until it is needed for the evaluation
4444 * result (which might be never)
4545 *
46- * This idea is implemented in a new class, the `Stream `.
46+ * This idea is implemented in a new class, the `LazyList `.
4747 *
48- * Streams are similar to lists, but their tail is evaluated only ''on demand''.
48+ * LazyLists are similar to lists, but their elements are evaluated only ''on demand''.
4949 *
50- * = Defining Streams =
50+ * = Defining LazyLists =
5151 *
52- * Streams are defined from a constant `Stream.empty` and a constructor `Stream .cons`.
52+ * LazyLists are defined from a constructor `LazyList .cons`.
5353 *
5454 * For instance,
5555 *
5656 * {{{
57- * val xs = Stream .cons(1, Stream .cons(2, Stream .empty))
57+ * val xs = LazyList .cons(1, LazyList .cons(2, LazyList .empty))
5858 * }}}
5959 *
60- * = Stream Ranges =
60+ * = LazyList Ranges =
6161 *
62- * Let's try to write a function that returns a `Stream ` representing a range of numbers
62+ * Let's try to write a function that returns a `LazyList ` representing a range of numbers
6363 * between `lo` and `hi`:
6464 *
6565 * {{{
66- * def streamRange (lo: Int, hi: Int): Stream [Int] =
67- * if (lo >= hi) Stream .empty
68- * else Stream .cons(lo, streamRange (lo + 1, hi))
66+ * def llRange (lo: Int, hi: Int): LazyList [Int] =
67+ * if (lo >= hi) LazyList .empty
68+ * else LazyList .cons(lo, llRange (lo + 1, hi))
6969 * }}}
7070 *
7171 * Compare to the same function that produces a list:
@@ -79,113 +79,122 @@ object LazyEvaluation extends ScalaTutorialSection {
7979 * The functions have almost identical structure yet they evaluate quite differently.
8080 *
8181 * - `listRange(start, end)` will produce a list with `end - start` elements and return it.
82- * - `streamRange (start, end)` returns a single object of type `Stream ` with `start` as head element.
82+ * - `llRange (start, end)` returns a single object of type `LazyList ` with `start` as head element.
8383 * - The other elements are only computed when they are needed, where
8484 * “needed” means that someone calls `tail` on the stream.
8585 *
86- * = Methods on Streams =
86+ * = Methods on LazyLists =
8787 *
88- * `Stream ` supports almost all methods of `List`.
88+ * `LazyList ` supports almost all methods of `List`.
8989 *
9090 * For instance, to find the second prime number between 1000 and 10000:
9191 *
9292 * {{{
93- * (streamRange (1000, 10000) filter isPrime)(1)
93+ * (llRange (1000, 10000) filter isPrime)(1)
9494 * }}}
9595 *
9696 * The one major exception is `::`.
9797 *
98- * `x :: xs` always produces a list, never a stream .
98+ * `x :: xs` always produces a list, never a lazy list .
9999 *
100- * There is however an alternative operator `#::` which produces a stream .
100+ * There is however an alternative operator `#::` which produces a lazy list .
101101 *
102102 * {{{
103- * x #:: xs == Stream .cons(x, xs)
103+ * x #:: xs == LazyList .cons(x, xs)
104104 * }}}
105105 *
106106 * `#::` can be used in expressions as well as patterns.
107107 *
108- * = Implementation of Streams =
108+ * = Implementation of LazyLists =
109109 *
110- * The implementation of streams is quite close to the one of lists.
110+ * The implementation of lazy lists is quite close to the one of lists.
111111 *
112- * Here's the trait `Stream `:
112+ * Here's the class `LazyList `:
113113 *
114114 * {{{
115- * trait Stream[+T] extends Seq[T] {
116- * def isEmpty: Boolean
117- * def head: T
118- * def tail: Stream[T]
119- * …
115+ * final class LazyList[+A] ... extends ... {
116+ * override def isEmpty: Boolean = ...
117+ * override def head: A = ...
118+ * override def tail: LazyList[A] = ...
119+ * ...
120120 * }
121121 * }}}
122122 *
123123 * As for lists, all other methods can be defined in terms of these three.
124124 *
125- * Concrete implementations of streams are defined in the `Stream ` companion object.
125+ * Concrete implementations of streams are defined in the `LazyList.State ` companion object.
126126 * Here's a first draft:
127127 *
128128 * {{{
129- * object Stream {
130- * def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
131- * def isEmpty = false
132- * def head = hd
133- * def tail = tl
134- * override def toString = "Stream(" + hd + ", ?)"
135- * }
136- * val empty = new Stream[Nothing] {
137- * def isEmpty = true
138- * def head = throw new NoSuchElementException("empty.head")
139- * def tail = throw new NoSuchElementException("empty.tail")
140- * override def toString = "Stream()"
129+ * private object State {
130+ * object Empty extends State[Nothing] {
131+ * def head: Nothing = throw new NoSuchElementException("head of empty lazy list")
132+ * def tail: LazyList[Nothing] = throw new UnsupportedOperationException("tail of empty lazy list")
141133 * }
134+ *
135+ * final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
142136 * }
143137 * }}}
144138 *
145- * The only important difference between the implementations of `List` and `Stream `
146- * concern `tl `, the second parameter of `Stream .cons`.
139+ * The only important difference between the implementations of `List` and `LazyList `
140+ * concern `tail `, the second parameter of `LazyList .cons`.
147141 *
148- * For streams , this is a by-name parameter: the type of `tl ` starts with `=>`. In such
142+ * For lazy lists , this is a by-name parameter: the type of `tail ` starts with `=>`. In such
149143 * a case, this parameter is evaluated by following the rules of the call-by-name model.
150144 *
151- * That's why the second argument to `Stream .cons` is not evaluated at the point of call.
145+ * That's why the second argument to `LazyList .cons` is not evaluated at the point of call.
152146 *
153- * Instead, it will be evaluated each time someone calls `tail` on a `Stream ` object.
147+ * Instead, it will be evaluated each time someone calls `tail` on a `LazyList ` object.
154148 *
155- * The other stream methods are implemented analogously to their list counterparts.
149+ * In Scala 2.13, LazyList (previously Stream) became fully lazy from head to tail. To make it possible,
150+ * methods (`filter`, `flatMap`...) are implemented in a way where the head is not being evaluated if is
151+ * not explicitly indicated.
156152 *
157153 * For instance, here's `filter`:
158154 *
159155 * {{{
160- * class Stream[+T] {
161- * …
162- * def filter(p: T => Boolean): Stream[T] =
163- * if (isEmpty) this
164- * else if (p(head)) cons(head, tail.filter(p))
165- * else tail.filter(p)
156+ * object LazyList extends SeqFactory[LazyList] {
157+ * ...
158+ * private def filterImpl[A](ll: LazyList[A], p: A => Boolean, isFlipped: Boolean): LazyList[A] = {
159+ * // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
160+ * var restRef = ll // val restRef = new ObjectRef(ll)
161+ * newLL {
162+ * var elem: A = null.asInstanceOf[A]
163+ * var found = false
164+ * var rest = restRef // var rest = restRef.elem
165+ * while (!found && !rest.isEmpty) {
166+ * elem = rest.head
167+ * found = p(elem) != isFlipped
168+ * rest = rest.tail
169+ * restRef = rest // restRef.elem = rest
170+ * }
171+ * if (found) sCons(elem, filterImpl(rest, p, isFlipped)) else State.Empty
172+ * }
166173 * }
167174 * }}}
168175 *
169176 * = Exercise =
170177 *
171- * Consider the following modification of `streamRange`. When you write
172- * `streamRange(1, 10).take(3).toList` what is the value of `rec`?
178+ * Consider the following modification of `llRange`. When you write
179+ * `llRange(1, 10).take(3).toList` what is the value of `rec`?
180+ *
181+ * Be careful, head is evaluating too!
173182 */
174- def streamRangeExercise (res0 : Int ): Unit = {
183+ def llRangeExercise (res0 : Int ): Unit = {
175184 var rec = 0
176- def streamRange (lo : Int , hi : Int ): Stream [Int ] = {
185+ def llRange (lo : Int , hi : Int ): LazyList [Int ] = {
177186 rec = rec + 1
178- if (lo >= hi) Stream .empty
179- else Stream .cons(lo, streamRange (lo + 1 , hi))
187+ if (lo >= hi) LazyList .empty
188+ else LazyList .cons(lo, llRange (lo + 1 , hi))
180189 }
181- streamRange (1 , 10 ).take(3 ).toList
190+ llRange (1 , 10 ).take(3 ).toList
182191 rec shouldBe res0
183192 }
184193
185194 /**
186195 * = Lazy Evaluation =
187196 *
188- * The proposed `Stream ` implementation suffers from a serious potential performance
197+ * The proposed `LazyList ` implementation suffers from a serious potential performance
189198 * problem: If `tail` is called several times, the corresponding stream
190199 * will be recomputed each time.
191200 *
@@ -210,18 +219,6 @@ object LazyEvaluation extends ScalaTutorialSection {
210219 * lazy val x = expr
211220 * }}}
212221 *
213- * = Lazy Vals and Streams =
214- *
215- * Using a lazy value for `tail`, `Stream.cons` can be implemented more efficiently:
216- *
217- * {{{
218- * def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
219- * def head = hd
220- * lazy val tail = tl
221- * …
222- * }
223- * }}}
224- *
225222 * == Exercise ==
226223 */
227224 def lazyVal (res0 : String ): Unit = {
0 commit comments