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.