Laziness and deferred execution
To deal with excessive copying, we can resort to a feature called deferred processing, also known as, lazy collections. A collection is lazy when all of its elements are not realized at the time of creation. Instead, elements are computed on demand.
Let's write a program to generate numbers from 1 to 100. We wish to check which numbers are evenly divisible by 2, 3, 4, and 5.
Let's generate a lazy collection of the input numbers:
scala> val list = (1 to 100).toList.view list: scala.collection.SeqView[Int,List[Int]] = SeqView(...)
We convert an existing Scala collection into a lazy one by calling the view function. Note that the list elements are not printed out, as these are not yet computed.
The following snippet shows a very simple predicate method that checks whether the number n is evenly divisible by d:
scala> def isDivisibleBy(d: Int)(n: Int) = {
| println(s"Checking ${n} by ${d}")
| n % d == 0
| }
isDivisibleBy: (d: Int)(n: Int)Boolean
We write a method isDivisibleBy in the curried form. We have written the isDivisibleBy as a series of functions, each function taking one argument. In our case, n is 2. We do this so we can partially apply functions to the divisor argument. This form helps us easily generate functions for divisors 2, 3, 4, and 5:
scala> val by2 = isDivisibleBy(2) _ by2: Int => Boolean = <function1> scala> val by3 = isDivisibleBy(3) _ by3: Int => Boolean = <function1> scala> val by4 = isDivisibleBy(4) _ by4: Int => Boolean = <function1> scala> val by5 = isDivisibleBy(5) _ by5: Int => Boolean = <function1>
We can test the preceding functions by entering the code on the REPL, as shown here:
scala> by3(9) Checking 9 by 3 res2: Boolean = true scala> by4(11) Checking 11 by 4 res3: Boolean = false
Now we write our checker:
scala> val result = list filter by2 filter by3 filter by4 filter by5 result: scala.collection.SeqView[Int,List[Int]] = SeqViewFFFF(...) scala> result.force Checking 1 by 2 Checking 2 by 2 Checking 2 by 3 Checking 3 by 2 Checking 4 by 2 ... Checking 60 by 2 Checking 60 by 3 Checking 60 by 4 Checking 60 by 5 ... res1: List[Int] = List(60)
Note that when 2 is checked by 2 and okayed, it is checked by 3. All the checks happen at the same time and the copying is elided.
Note the force method; this is the opposite of the view method. The force method converts the collection back into a strict one. For a strict collection, all the elements are processed. Once the processing is done, a collection with just the number 60 is returned.