@@ -328,4 +328,211 @@ object StrictnessAndLazinessSection extends FlatSpec with Matchers with org.scal
328
328
step7 shouldBe step8
329
329
step8 shouldBe finalStep
330
330
}
331
+
332
+ /**
333
+ * = Infinite streams and corecursion =
334
+ *
335
+ * Because they’re incremental, the functions we’ve written also work for `infinite streams`. Here’s an example of
336
+ * an infinite `Stream` of `1`s:
337
+ *
338
+ * {{{
339
+ * val ones: Stream[Int] = Stream.cons(1, ones)
340
+ * }}}
341
+ *
342
+ * Although ones is infinite, the functions we’ve written so far only inspect the portion of the stream needed to
343
+ * generate the demanded output. For example:
344
+ */
345
+
346
+ def streamOnesAssert (res0 : List [Int ], res1 : Boolean , res2 : Boolean , res3 : Boolean ): Unit = {
347
+ ones.take(5 ).toList shouldBe res0
348
+ ones.exists(_ % 2 != 0 ) shouldBe res1
349
+ ones.map(_ + 1 ).exists(_ % 2 == 0 ) shouldBe res2
350
+ ones.forAll(_ != 1 ) shouldBe res3
351
+ }
352
+
353
+ /**
354
+ * Let's generalize `ones` slightly to the function `constant`, which returns an infinite `Stream` of a given value:
355
+ *
356
+ * {{{
357
+ * def constant[A](a: A): Stream[A] = {
358
+ * lazy val tail: Stream[A] = Cons(() => a, () => tail)
359
+ * tail
360
+ * }
361
+ * }}}
362
+ *
363
+ * Of course, we can generate `number series` with `Stream`s. For example, let's write a function that generates an
364
+ * infinite stream of integers (n, n + 1, n + 2...):
365
+ */
366
+
367
+ def streamIntegersAssert (res0 : Int ): Unit = {
368
+ def from (n : Int ): Stream [Int ] =
369
+ cons(n, from(n + res0))
370
+
371
+ from(100 ).take(5 ).toList shouldBe List (100 , 101 , 102 , 103 , 104 )
372
+ }
373
+
374
+ /**
375
+ * We can also create a function `fibs` that generates the infinite stream of Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8...
376
+ */
377
+
378
+ def streamFibsAssert (res0 : Int , res1 : Int ): Unit = {
379
+ val fibs = {
380
+ def go (f0 : Int , f1 : Int ): Stream [Int ] =
381
+ cons(f0, go(f1, f0+ f1))
382
+ go(res0, res1)
383
+ }
384
+
385
+ fibs.take(7 ).toList shouldBe List (0 , 1 , 1 , 2 , 3 , 5 , 8 )
386
+ }
387
+
388
+ /**
389
+ * Now we're going to write a more general stream-building function called `unfold`. It takes an initial state, and
390
+ * a function for producing both the next state and the next value in the generated stream:
391
+ *
392
+ * {{{
393
+ * def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] = f(z) match {
394
+ * case Some((h,s)) => cons(h, unfold(s)(f))
395
+ * case None => empty
396
+ * }
397
+ * }}}
398
+ *
399
+ * `Option` is used to indicate when the `Stream` should be terminated, if at all. Now that we have `unfold`, let's
400
+ * re-write our previous generator functions based on it, starting from `fibs`:
401
+ */
402
+
403
+ def streamFibsViaUnfoldAssert (res0 : Int , res1 : Int ): Unit = {
404
+ val fibsViaUnfold =
405
+ unfold((res0, res1)) { case (f0,f1) => Some ((f0, (f1, f0+ f1))) }
406
+
407
+ fibsViaUnfold.take(7 ).toList shouldBe List (0 , 1 , 1 , 2 , 3 , 5 , 8 )
408
+ }
409
+
410
+ /**
411
+ * `from` follows a similar principle, albeit a little bit more simple:
412
+ */
413
+
414
+ def streamFromViaUnfoldAssert (res0 : Int ): Unit = {
415
+ def fromViaUnfold (n : Int ) = unfold(n)(n => Some ((n, n + res0)))
416
+
417
+ fromViaUnfold(100 ).take(5 ).toList shouldBe List (100 , 101 , 102 , 103 , 104 )
418
+ }
419
+
420
+ /**
421
+ * Again, `constant` can be implemented in terms of `unfold` in a very similar way:
422
+ *
423
+ * {{{
424
+ * def constantViaUnfold[A](a: A) = unfold(a)(_ => Some((a,a)))
425
+ * }}}
426
+ *
427
+ * Follow the same pattern to implement `ones` using `unfold`:
428
+ */
429
+
430
+ def streamOnesViaUnfoldAssert (res0 : Some [(Int , Int )]): Unit = {
431
+ val onesViaUnfold = unfold(1 )(_ => res0)
432
+
433
+ onesViaUnfold.take(10 ).toList shouldBe List (1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 )
434
+ }
435
+
436
+ /**
437
+ * Now we're going to re-implement some of the higher-order functions for `Stream`s, starting with map:
438
+ *
439
+ * {{{
440
+ * def mapViaUnfold[B](f: A => B): Stream[B] = unfold(this) {
441
+ * case Cons(h,t) => Some((f(h()), t()))
442
+ * case _ => None
443
+ * }
444
+ * }}}
445
+ *
446
+ * `take` can also be re-written via `unfold`, let's try it:
447
+ */
448
+
449
+ def streamTakeViaUnfold (res0 : Int , res1 : Int ): Unit = {
450
+ def takeViaUnfold [A ](s : Stream [A ], n : Int ): Stream [A ] =
451
+ unfold((s, n)) {
452
+ case (Cons (h,t), 1 ) => Some ((h(), (Stream .empty, res0)))
453
+ case (Cons (h,t), n) if n > 1 => Some ((h(), (t(), n - 1 )))
454
+ case _ => None
455
+ }
456
+
457
+ takeViaUnfold(Stream (1 , 2 , 3 , 4 , 5 ), 5 ).toList shouldBe List (1 , 2 , 3 , 4 , 5 )
458
+ }
459
+
460
+ /**
461
+ * Let's continue by re-writting `takeWhile` in terms of `unfold`:
462
+ *
463
+ * {{{
464
+ * def takeWhileViaUnfold(f: A => Boolean): Stream[A] = unfold(this) {
465
+ * case Cons(h,t) if f(h()) => Some((h(), t()))
466
+ * case _ => None
467
+ * }
468
+ * }}}
469
+ *
470
+ * We can also bring back functions we saw with `List`s, as `zipWith`:
471
+ *
472
+ * {{{
473
+ * def zipWith[B,C](s2: Stream[B])(f: (A,B) => C): Stream[C] = unfold((this, s2)) {
474
+ * case (Cons(h1,t1), Cons(h2,t2)) =>
475
+ * Some((f(h1(), h2()), (t1(), t2())))
476
+ * case _ => None
477
+ * }
478
+ * }}}
479
+ *
480
+ * `zipAll` can also be implemented using `unfold`. Note that it should continue the traversal as long as either
481
+ * stream has more elements - it uses `Option` to indicate whether each stream has been exhausted:
482
+ *
483
+ * {{{
484
+ * def zipAll[B](s2: Stream[B]): Stream[(Option[A],Option[B])] = zipWithAll(s2)((_,_))
485
+ *
486
+ * def zipWithAll[B, C](s2: Stream[B])(f: (Option[A], Option[B]) => C): Stream[C] =
487
+ * Stream.unfold((this, s2)) {
488
+ * case (Empty, Empty) => None
489
+ * case (Cons(h, t), Empty) => Some(f(Some(h()), Option.empty[B]) -> (t(), empty[B]))
490
+ * case (Empty, Cons(h, t)) => Some(f(Option.empty[A], Some(h())) -> (empty[A] -> t()))
491
+ * case (Cons(h1, t1), Cons(h2, t2)) => Some(f(Some(h1()), Some(h2())) -> (t1() -> t2()))
492
+ * }
493
+ * }}}
494
+ *
495
+ * Now we're going to try to implement `startsWith` using the functions we've seen so far:
496
+ *
497
+ * {{{
498
+ * def startsWith[A](s: Stream[A]): Boolean =
499
+ * zipAll(s).takeWhile(!_._2.isEmpty) forAll {
500
+ * case (h,h2) => h == h2
501
+ * }
502
+ * }}}
503
+ *
504
+ * We can also write `tails` using `unfold`. For a given Stream, `tails` returns the `Stream` of suffixes of the input
505
+ * sequence, starting with the original `Stream`. For example, given `Stream(1,2,3)`, it would return
506
+ * `Stream(Stream(1,2,3), Stream(2,3), Stream(3), Stream())`.
507
+ */
508
+
509
+ def streamTailsAssert (res0 : Int ): Unit = {
510
+ def tails [A ](s : Stream [A ]): Stream [Stream [A ]] =
511
+ unfold(s) {
512
+ case Empty => None
513
+ case s1 => Some ((s1, s1 drop res0))
514
+ } append Stream (Stream .empty)
515
+
516
+ tails(Stream (1 , 2 , 3 )).toList.map(_.toList) shouldBe List (List (1 , 2 , 3 ), List (2 , 3 ), List (3 ), List ())
517
+ }
518
+
519
+ /**
520
+ * We can generalize tails to the function scanRight, which is like a `foldRight` that returns a stream of the
521
+ * intermediate results:
522
+ *
523
+ * {{{
524
+ * def scanRight[B](z: B)(f: (A, => B) => B): Stream[B] =
525
+ * foldRight((z, Stream(z)))((a, p0) => {
526
+ * lazy val p1 = p0
527
+ * val b2 = f(a, p1._1)
528
+ * (b2, cons(b2, p1._2))
529
+ * })._2
530
+ * }}}
531
+ *
532
+ * For example:
533
+ */
534
+
535
+ def streamScanRightAssert (res0 : List [Int ]): Unit = {
536
+ Stream (1 , 2 , 3 ).scanRight(0 )(_ + _).toList shouldBe res0
537
+ }
331
538
}
0 commit comments