@@ -43,29 +43,29 @@ object LazyEvaluation extends ScalaTutorialSection {
43
43
* - Avoid computing the tail of a sequence until it is needed for the evaluation
44
44
* result (which might be never)
45
45
*
46
- * This idea is implemented in a new class, the `Stream `.
46
+ * This idea is implemented in a new class, the `LazyList `.
47
47
*
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''.
49
49
*
50
- * = Defining Streams =
50
+ * = Defining LazyLists =
51
51
*
52
- * Streams are defined from a constant `Stream.empty` and a constructor `Stream .cons`.
52
+ * LazyLists are defined from a constructor `LazyList .cons`.
53
53
*
54
54
* For instance,
55
55
*
56
56
* {{{
57
- * val xs = Stream .cons(1, Stream .cons(2, Stream .empty))
57
+ * val xs = LazyList .cons(1, LazyList .cons(2, LazyList .empty))
58
58
* }}}
59
59
*
60
- * = Stream Ranges =
60
+ * = LazyList Ranges =
61
61
*
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
63
63
* between `lo` and `hi`:
64
64
*
65
65
* {{{
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))
69
69
* }}}
70
70
*
71
71
* Compare to the same function that produces a list:
@@ -79,113 +79,122 @@ object LazyEvaluation extends ScalaTutorialSection {
79
79
* The functions have almost identical structure yet they evaluate quite differently.
80
80
*
81
81
* - `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.
83
83
* - The other elements are only computed when they are needed, where
84
84
* “needed” means that someone calls `tail` on the stream.
85
85
*
86
- * = Methods on Streams =
86
+ * = Methods on LazyLists =
87
87
*
88
- * `Stream ` supports almost all methods of `List`.
88
+ * `LazyList ` supports almost all methods of `List`.
89
89
*
90
90
* For instance, to find the second prime number between 1000 and 10000:
91
91
*
92
92
* {{{
93
- * (streamRange (1000, 10000) filter isPrime)(1)
93
+ * (llRange (1000, 10000) filter isPrime)(1)
94
94
* }}}
95
95
*
96
96
* The one major exception is `::`.
97
97
*
98
- * `x :: xs` always produces a list, never a stream .
98
+ * `x :: xs` always produces a list, never a lazy list .
99
99
*
100
- * There is however an alternative operator `#::` which produces a stream .
100
+ * There is however an alternative operator `#::` which produces a lazy list .
101
101
*
102
102
* {{{
103
- * x #:: xs == Stream .cons(x, xs)
103
+ * x #:: xs == LazyList .cons(x, xs)
104
104
* }}}
105
105
*
106
106
* `#::` can be used in expressions as well as patterns.
107
107
*
108
- * = Implementation of Streams =
108
+ * = Implementation of LazyLists =
109
109
*
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.
111
111
*
112
- * Here's the trait `Stream `:
112
+ * Here's the class `LazyList `:
113
113
*
114
114
* {{{
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
+ * ...
120
120
* }
121
121
* }}}
122
122
*
123
123
* As for lists, all other methods can be defined in terms of these three.
124
124
*
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.
126
126
* Here's a first draft:
127
127
*
128
128
* {{{
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")
141
133
* }
134
+ *
135
+ * final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
142
136
* }
143
137
* }}}
144
138
*
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`.
147
141
*
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
149
143
* a case, this parameter is evaluated by following the rules of the call-by-name model.
150
144
*
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.
152
146
*
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.
154
148
*
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.
156
152
*
157
153
* For instance, here's `filter`:
158
154
*
159
155
* {{{
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
+ * }
166
173
* }
167
174
* }}}
168
175
*
169
176
* = Exercise =
170
177
*
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!
173
182
*/
174
- def streamRangeExercise (res0 : Int ): Unit = {
183
+ def llRangeExercise (res0 : Int ): Unit = {
175
184
var rec = 0
176
- def streamRange (lo : Int , hi : Int ): Stream [Int ] = {
185
+ def llRange (lo : Int , hi : Int ): LazyList [Int ] = {
177
186
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))
180
189
}
181
- streamRange (1 , 10 ).take(3 ).toList
190
+ llRange (1 , 10 ).take(3 ).toList
182
191
rec shouldBe res0
183
192
}
184
193
185
194
/**
186
195
* = Lazy Evaluation =
187
196
*
188
- * The proposed `Stream ` implementation suffers from a serious potential performance
197
+ * The proposed `LazyList ` implementation suffers from a serious potential performance
189
198
* problem: If `tail` is called several times, the corresponding stream
190
199
* will be recomputed each time.
191
200
*
@@ -210,18 +219,6 @@ object LazyEvaluation extends ScalaTutorialSection {
210
219
* lazy val x = expr
211
220
* }}}
212
221
*
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
- *
225
222
* == Exercise ==
226
223
*/
227
224
def lazyVal (res0 : String ): Unit = {
0 commit comments