Instructor:
How can we apply the concepts of strictness and non-strictness to folds?
Sum a list of Ints: Old-School Iteration
// Setupval ints: List[Int] = (0 to 10_000).toList// Sum: Old school iterationvar sumIt: Int = 0for i <- ints do sumIt += i
var sumIt: Int = 50005000
Sum a list of Ints: Scala Collections Fold
val ints: List[Int] = (0 to 10_000).toListval sumFold = ints.fold(0)(_ + _)
val sumFold: Int = 50005000
from the left
val ints: List[Int] = (0 to 10).toListval sumL = ints.foldLeft(0)(_ + _)
val sumL: Int = 55
from the right
val ints: List[Int] = (0 to 10).toListval sumR = ints.foldRight(0)(_ + _)
val sumR: Int = 55
fold
How does Scala implement fold?
val ints: List[Int] = (0 to 10).toListints.fold(0): (x, y) => ???
val ints: List[Int] = (0 to 10).toListints.fold(0): (x, y) => throw new Exception() // Show me the call stack involved in executing this function!
java.lang.Exception at rs$line$27$.$init$$$anonfun$1(rs$line$27:2) at scala.runtime.java8.JFunction2$mcIII$sp.apply(JFunction2$mcIII$sp.scala:17) at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:183) at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:179) at scala.collection.immutable.List.foldLeft(List.scala:79) at scala.collection.IterableOnceOps.fold(IterableOnce.scala:792) at scala.collection.IterableOnceOps.fold$(IterableOnce.scala:792) at scala.collection.AbstractIterable.fold(Iterable.scala:935) ... 33 elided
package scala.collectiontrait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => // . . . def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) // . . .}
How does Scala implement foldLeft and foldRight?
foldLeft
foldRight
package scala.collectiontrait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] extends Any with SeqOps[A, CC, C] { // . . . override def foldLeft[B](z: B)(op: (B, A) => B): B = { var acc = z var these: LinearSeq[A] = coll while (!these.isEmpty) { // <-- Ed.: Lipstick on a pig. acc = op(acc, these.head) these = these.tail } acc } }
var
while
package scala.collection.immutable.Listsealed abstract class List[+A] extends /* . . . */ { // . . . final override def foldRight[B](z: B)(op: (A, B) => B): B = { var acc = z var these: List[A] = reverse // <-- Ed.: Lipstick on a pig. while (!these.isEmpty) { // <------ Airbrushed! acc = op(these.head, acc) these = these.tail } acc } // . . . final override def reverse: List[A] = { var result: List[A] = Nil var these = this while (!these.isEmpty) { result = these.head :: result these = these.tail } result } // . . . }
reverse
Find an Element (Linear Find)
// Setupval intArr: Array[Int] = (0 to 100).toArray // Need random access!// Find: Old school iterationval key = 7var idx: Int = -1var i = 0while i < intArr.length && idx == -1 do if intArr(i) == key then idx = i i += 1
val intArr: Array[Int] = // . . .val key: Int = 7var idx: Int = 7var i: Int = 8
Why can't I just break, as in Java and C?
break
val intArr: Array[Int] = (0 to 100).toArray // Need random access!val key = 7var idx: Int = -1var i = 0while i < intArr.length do if intArr(i) == key then idx = i // Gee, wouldn't it be nice if I could break here? i += 1
val intArr: Array[Int] = // . . . val key: Int = 7 var idx: Int = 7 var i: Int = 101
import scala.util.control.*val intArr: Array[Int] = (0 to 100).toArray // Need random access!val loop = new Breaksval key = 7var idx: Int = -1var i = 0loop.breakable: while(i < intArr.length) do if intArr(i) == key then idx = i loop.break i += 1 i // Tell me the value of i.
val intArr: Array[Int] = // . . .val loop: scala.util.control.Breaks = scala.util.control.Breaks@14e83c9dval key: Int = 7var idx: Int = 7
Breaks
How do I find with a foldLeft?
find
val ints: List[Int] = (0 to 99_999).toListval key = 42ints.foldLeft[(Int, Int)](0, -1): case ((i, k), _) if k != -1 => (i + 1, k) case ((i, k), n) if n == key => (i + 1, i) case ((i, _), _) => (i + 1, -1)
val ints: List[Int] = // . . . val key: Int = 42 val res5: (Int, Int) = (100000,42)
List
How about recursion?
val ints: List[Int] = (0 to 99_999).toListval key = 42import scala.annotation.tailrecdef find(key: Int, intList: List[Int]): Int = @tailrec def search(i: Int, remaining: List[Int]): Int = remaining match case Nil => -1 case (x :: xs) if x == key => i // Yay, I can stop! case (_ :: xs) => search(i + 1, xs) search(0, intList)find(key, ints)
val ints: List[Int] = // . . .val key: Int = 42val res6: Int = 42
val ints: List[Int] = (1 to Int.MaxValue).toList // N.B. Range is lazy!
java.lang.OutOfMemoryError: Java heap space: failed reallocation of scalar replaced objects at java.base/java.lang.Integer.valueOf(Integer.java:1005) at scala.runtime.BoxesRunTime.boxToInteger(BoxesRunTime.java:63) at scala.collection.immutable.RangeIterator.next(Range.scala:641) at scala.collection.immutable.List.prependedAll(List.scala:156) at scala.collection.IterableOnceOps.toList(IterableOnce.scala:1446) at scala.collection.IterableOnceOps.toList$(IterableOnce.scala:1446) at scala.collection.AbstractIterable.toList(Iterable.scala:935)
LazyList
val lazy1 = LazyList.from(1)lazy1.tail.headlazy1val lazyListOf5 = lazy1.take(5)val listOf5 = lazyListOf5.toListlazyListOf5
val lazy1: LazyList[Int] = LazyList(<not computed>)
val res2: Int = 2
val res3: LazyList[Int] = LazyList(1, 2, <not computed>) // 1 and 2 are memoized!
val lazyListOf5: LazyList[Int] = LazyList(<not computed>)
val listOf5: List[Int] = List(1, 2, 3, 4, 5)
val res4: LazyList[Int] = LazyList(1, 2, 3, 4, 5) // 1, 2, 3, 4, 5 are memoized!
val lazyInts: LazyList[Int] = LazyList.from(0)val key = 7opaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)val indexedInts = lazyInts.zipWithIndex.map((n, i) => n -> Index(i))indexedInts.foldLeft(NotFound): case (k, _) if k != NotFound => k case (_, (n, i)) if n == key => Index(i) case _ => NotFound
val lazyInts: LazyList[Int] = LazyList(<not computed>)
val key: Int = 7
// defined object Index
val NotFound: Index = -1
val indexedInts: LazyList[(Int, Int)] = LazyList(<not computed>)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
val lazyInts: LazyList[Int] = LazyList.from(0)val key = 7opaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)val indexedInts = lazyInts.zipWithIndex.map((n, i) => n -> Index(i))indexedInts.foldRight(NotFound): case (_, k) if k != NotFound => k case ((n, i), _) if n == key => Index(i) case _ => NotFound
java.lang.OutOfMemoryError: Java heap space
How are LazyList's folds defined?
// foldLeft is defined in class// scala.collection.immutable.LazyList@tailrecoverride def foldLeft[B](z: B)(op: (B, A) => B): B = if (isEmpty) z else tail.foldLeft(op(z, head))(op)
// foldRight is defined in trait// scala.collection.IterableOnceOpsdef foldRight[B](z: B)(op: (A, B) => B): B = reversed.foldLeft(z)((b, a) => op(a, b))protected def reversed: Iterable[A] = { var xs: immutable.List[A] = immutable.Nil val it = iterator while (it.hasNext) xs = it.next() :: xs xs}
// fold is defined in trait// scala.collection.IterableOnceOpsdef fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) // dynamic dispatch to LazyList!
tail
Haskell-style Fold Left
import scala.annotation.tailrecopaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)// Translation from Haskell.@tailrecdef foldl[A, B](f: (=> B) => A => B)(z: => B)(a: LazyList[A]): B =a match case LazyList() => z // LazyList() analagous to Nil (sort of) case x#::xs => foldl(f)(f(z)(x))(xs) // #:: is how we pattern match with LazyList
def findLeft(key: Int)(indexedInts: LazyList[(Int, Index)]): Index = def check(findRest: => Index)(next: (Int, Index)): Index = next match case (n, i) if key == n => i case _ => findRest foldl(check)(NotFound)(indexedInts) val lazyInts: LazyList[Int] = LazyList.from(0) val indexedInts = lazyInts.zipWithIndex.map((n, i) => n -> Index(i))findLeft(7)(indexedInts)
def foldl[A, B](f: (=> B) => A => B)(z: => B)(a: LazyList[A]): B
def findLeft(key: Int)(indexedInts: LazyList[(Int, Index)]): Index
val indexedInts: LazyList[(Int, Index)] = LazyList(<not computed>)
Haskell-style Fold Right
import scala.annotation.tailrecopaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)// Translation from Haskell.def foldr[A, B](f: A => (=> B) => B)(z: => B)(a: LazyList[A]): B = a match case LazyList() => z // LazyList() analagous to Nil (sort of) case x#::xs => f(x)(foldr(f)(z)(xs)) // #:: is how we pattern match with LazyList
def findRight(key: Int)(indexedInts: LazyList[(Int, Index)]): Index = def check(next: (Int, Index))(findRest: => Index): Index = next match case (n, i) if key == n => i case _ => findRest foldr(check)(NotFound)(indexedInts) val lazyInts: LazyList[Int] = LazyList.from(0) val indexedInts = lazyInts.zipWithIndex.map((n, i) => n -> Index(i))findRight(7)(indexedInts)
def foldr[A, B](f: A => (=> B) => B)(z: => B)(a: LazyList[A]): B
def findRight(key: Int)(indexedInts: LazyList[(Int, Index)]): Index
val res3: Index = 7
foldr
findRight with foldr evaluation
findRight
opaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)// Translation from Haskell.def foldr[A, B](f: A => (=> B) => B)(z: => B)(a: LazyList[A]): B = a match case LazyList() => z // LazyList() analagous to Nil (sort of) case x#::xs => f(x)(foldr(f)(z)(xs)) // #:: is how we pattern match with LazyList
findRight(7)(indexedInts)
foldr(check)(NotFound)(0..)
check((0, Index(0)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(1..)check((1, Index(1)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(2..)check((2, Index(2)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(3..)check((3, Index(3)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(4..)check((4, Index(4)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(5..)check((5, Index(5)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(6..)check((6, Index(6)))(foldr(check)(NotFound)(..))
foldr(check)(NotFound)(7..)check((7, Index(7)))(foldr(check)(NotFound)(..))
Index(7)
findLeft with foldl evaluation
findLeft
foldl
import scala.annotation.tailrecopaque type Index = Intobject Index: def apply(i: Int): Index = ival NotFound = Index(-1)@tailrecdef foldl[A, B](f: (=> B) => A => B)(z: => B)(a: LazyList[A]): B = a match case LazyList() => z // LazyList() analagous to Nil (sort of) case x#::xs => foldl(f)(f(z)(x))(xs) // #:: is how we pattern match with LazyList
def findLeft(key: Int)(indexedInts: LazyList[(Int, Index)]): Index = def check(findRest: => Index)(next: (Int, Index)): Index = next match case (n, i) if key == n => i case _ => findRest foldl(check)(NotFound)(indexedInts)val lazyInts: LazyList[Int] = LazyList.from(0) val indexedInts = lazyInts.zipWithIndex.map((n, i) => n -> Index(i))findLeft(3)(indexedInts)
findLeft(3)(0..)
foldl(check)(NotFound)(0..)
foldl(check)(check(NotFound)((0, Index(0))))(1..)
foldl(check)(check(check(NotFound)((0, Index(0))))((1, Index(1))))(2..)
foldl(check)(check(check(check(NotFound)((0, Index(0))))((1, Index(1)))((2, Index(2)))(3..)
foldl(check)(check(check(check(check(NotFound)((0, Index(0))))((1, Index(1)))((2, Index(2)))((3, Index(3)))(4..)
...
check