Scala Foundation Course - Strict and Lazy Evaluations

Lazy evaluation is an important idea in Functional Programming world. The idea is simple to understand. However, it is a bit difficult to implement. The Strict means, evaluate the expression now and the lazy means, evaluate it on the first use.

Strict Evaluations

Let me start with the strict evaluation and show you some examples. I will try to explain the strict behaviour in three different scenarios.

  1. Variable assignment
  2. Function parameters
  3. Higher order functions

The first two scenarios are simple, and we have been using it for a long time. Let’s take simple example.

Strict evaluations in variable assignment

I will use the below code to demonstrate the concept.

    def Factorial(i:Int):Int={
        println("You called Factorial for "+i)
        def tFactorial(n:Int, f:Int):Int={
            if(n <=0)
                tFactorial(n-1, n*f)
    val s = Factorial(15)/Factorial(11)
    You called Factorial for 15
    You called Factorial for 11
    s: Int = 50  */                                   

We are initializing a value s with the result of an expression. Like most of the languages, Scala is a strict language. So, the value for s is evaluated immediately. The result of the expression is 50, and hence the s gets a value assignment as 50.
Since we are calling the Factorial function twice, you will see output message twice. Let me ask you a simple question.
What will happen if I print the s?


No matter, how many times we use the value s. We never see these You called Factorial messages again. Because the expression is strict, and Scala evaluates it just once. So, the s is initialized just once and then, we are reusing it.

Strict evaluations in function parameters

Now, let's come to the second scenario. What will happen if we pass an expression to a function? Let me show you.

    def twice(i:Int ) = {
        println("We haven't used i yet")
        i + i

The twice in the above example is a function. Right? It takes an integer and returns by adding it to itself. That's it. Let me call it and pass an expression as a parameter.

    twice(Factorial(15)/Factorial(11) )                                               

We are passing an expression to the function. Right? Scala evaluates the expression before it can pass the result to the function parameter. So, the parameter i is initialized in the beginning, and Scala never re-evaluates it inside the Function.

Strict evaluations in higher order functions

We are used to of the above two scenarios. There is nothing new, and we have seen such behaviour in every programming language that we learned. But this is not the case with Higher Order function. That's our third scenario.
The behaviour of a higher order function is different. Let me show you an example.

    def twice(f: => Int ) =  {
        println("We haven't used f yet")
        f + f

So, now the function twice is a higher order function. Right? It takes another function f as an input then execute the f twice and return the sum. That's it. Let me call the twice.

    twice({ Factorial(15)/Factorial(11) })
    We haven't used f yet
    You called Factorial for 15
    You called Factorial for 11
    You called Factorial for 15
    You called Factorial for 11
    res2: Int = 100 

So, what do you think? Scala is evaluating f twice.
If we use f ten times in our function, Scala will execute it ten times. You may argue that such behaviour is necessary for a parameter that is not a simple value but a function. I also agree with that. But if the parameter function is pure and it always returns the same value, this repeated execution is unnecessary and may cause a performance problem.
So, how can we avoid this thing? It is as simple as - cache the parameters in a val.

    def twice(f: => Int ) =  {
        val i = f
        println("We haven't used i yet")
        i + i

So, here is the new code. I am evaluating f once and caching the value as i. Then, every time I need f, I use the i. Right? This caching will save me from calling f several times.
You must have noticed that f also goes through a strict evaluation. Scala evaluates it immediately as soon as we refer it. That was the Strict evaluation. Let me summarize it.

Strict evaluation in Scala

The default method of expression evaluation in Scala is Strict.
If you are using a val to hold the value of an expression, it will evaluate only once. But the Evaluation is immediate. The same rule applies to function parameters as well, But the HO function is an exception to this rule of only once evaluation.
When you pass a function to a higher order function as a parameter, Scala will evaluate the expression on the use of the parameter instead of evaluating it immediately. The important point to remember is that the Scala evaluates the parameter function on every use. You can overcome that kind of multiple evaluations by caching the parameter in a val.

Lazy evaluations in Scala

The strict evaluation is the default behaviour in almost every programming language. But sometimes the strictness is not desired. In some cases, we don't want that to happen. That's where the Lazy evaluations play a role.
Lazy evaluations are one of the functional programming techniques for implementing the efficient code. So, almost every functional programming language supports the lazy evaluation. Scala also offers you the same. Let me show you.

    lazy val l = Factorial(15)/Factorial(11)                                               

We have seen this earlier without the keyword lazy. Right? But this time, you don't see those outputs saying, You called Factorial. That happens, because the Scala has not yet evaluated the value of l. We made it lazy by using the lazy keyword. Scala will not call these functions and evaluate the expression until we use the l. So, let's use it.


Okay, now we see these messages because Scala called these functions to evaluate the expression. Let me ask you a simple question.
What will happen if I print the l once again? Will you see these lines again? I mean, Is the expression re-evaluated?
No, right? Lazy val is just like a val. We can initialize it only once, and then we can't change it. We made it lazy because we wanted to initialize it on the first use. So, when we print it once again, it simply shows the value. The evaluation was already complete on the first use.
So, what is a lazy evaluation?
Scala evaluates lazy values at the time of its first use. I showed you the behaviour of lazy val. It doesn't evaluate until we use the val.

Lazy evaluations in higher order Scala functions

Now, let's check the impact of laziness on a higher order function.
Earlier, we implemented the twice function.Let me come back to the twice Function.

    def twice(f: => Int ) =  {
        val i = f
        println("We didn't use i yet")
        i + i

Ok, so we cached the f into a val i. Then we use the i for two times.


So, as we learned in a strict evaluation, Scala evaluates the function immediately even before we actually used the i. Now, let's see how to make it lazy?

    def twice(f: => Int ) =  {
        lazy val i = f
        println("We didn't use i yet")
        i + i

Simple, we cached the f into a lazy val i instead of a normal value. Let's test it.

    /* Output
    We didn't use i yet
    You called Factorial for 15
    You called Factorial for 11
    res3: Int = 100 */                                        

Have you noticed the difference? A strict parameter evaluates immediately. However, a lazy parameter executes on the first use. So, you can even make a function lazy. The trick is simple. Assign the Function to a lazy val.
That's all about strict and lazy evaluations.

Strict and Lazy evaluation in Scala

Let me summarize it. Scala supports strict and lazy evaluations.
The default method is the strict evaluation. That means Scala will evaluate the expression immediately. There is one performance issue with the strict evaluation in the case of a higher order function. If the parameter is a function, Scala evaluates it on every use. You can overcome this limitation by caching the parameter in a val.
Now the lazy evaluation.
You can make an expression lazy by using a lazy val. There is no explicit syntax to make a function parameter lazy. However, you can make a lazy function by caching it to a lazy val.
Now, you might be wondering about the benefits.

Why would you want to use the lazy evaluations?

The laziness is mostly used to create data structures to handle large volumes of data efficiently. Apache Spark libraries are the most common examples. If you know Spark RDD, they implement all transformations as lazy operations. We will cover Spark examples in our Spark tutorial but let me give you some glimpse of the benefits using simple Scala data structures.
I have a log file. There are different types of entries. We have notice, info, error and may be some other types. I am interested in looking at the first two error messages from the log file. Right? Let me repeat. We want to extract first two error messages. That's it.
Let's do it using Scala.

    val s = Source.fromFile("error_log").getLines().toList.filter(_.contains("[error]")).take(2) 

The above code gives me a Scala list with two items. You can print those lines using below command.

    s foreach println                                               

We got what we were looking for. Right? But did you notice that how we created this list?

    val s = Source.fromFile("error_log").getLines().toList                                               

We opened the file, got all the lines and converted it to a list. Then we filtered for all the lines that contained error. Finally, we take only first two of those lines. Assuming that my file is large, may be few GBs, Is this an efficient method? I mean, I can create a simple loop. Read the file line by line until I get two error lines and stop as soon as I get two error messages. That's more efficient. Isn't it. I may get first two error messages in the beginning and avoid time to process rest of the data.
But the above code is reading all the lines. It happens because the List is a strict data structure. When we call toList function, it evaluates immediately. So, we get all the lines from the file even before we apply a filter on it.
Scala gives you a lazy alternative to lists. They call it Stream.

    val s = Source.fromFile("error_log").getLines().toStream.filter(_.contains("[error]")).take(2)

We can do the same thing using a Stream. You can print the output using below command.

    s foreach  println                                               

Did you notice the difference? No, Right? Watch the video for step by step explanation.
This lazy behaviour is incredibly powerful. It not only delays the evaluation of individual operations but also combines them. You must have noticed the combining behaviour in this example. The Stream reads one line. Then it applies the filter on that particular line. If the line contains error, it takes the line and moves to the next line. This behaviour is opposite to the way list works. The list gets all the lines. Then it applies the filter to all the lines. The list is evaluating one operation at a time for all the lines. But the Stream combines multiple operations and implement all of them together on each line.
This kind of approach gives an advantage when we are dealing with large volumes of data and applying a series of operations.
In fact, it is so powerful that it allows us to create a data set of infinite length. We evaluate what we need, but the data set can be of an unlimited range.
Do you want an example of an Infinite Series? I have a simple example. Fibonacci series. I hope you are familiar with Fibonacci series.
We need to initialize the Fibonacci series with first two numbers, and then it can go on to infinity. I want to create a function in Scala that can generate a list of all Fibonacci numbers. Can we do this?
If we use a strict evaluation, the function will run out of memory for sure. The reason is straight. We can't create a list of infinite elements. We don't have a computer capacity for that. But we can do it using a lazy list because a lazy list will not evaluate all of the elements.
Let me show you the code.

    def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b)                                        

That's it. just one line. The function takes two parameters, and they are the first two numbers of the series. The function returns a Stream. You already know that Stream is the lazy list of Scala.
The body of the function is simple. This symbol is used to create a stream. We place this symbol between two elements of the Stream. So, a is the first element, and we get next element using the recursion. This recursion goes on forever because we don't have a termination condition. That's it.
You can create a series by calling a function.

    val f = fibFrom(1,2)                                              

And evaluate only as many as you want.

    f.takeWhile(_<10) foreach println                                               

So, the laziness is one of the powerful features of FP languages like Scala.
Thanks for watching learning journal.Keep learning and keep growing.

You will also like:

Scala Variable length arguments

How do you create a variable length argument in Scala? Why would you need it?

Learning Journal

Referential Transparency

Referential Transparency is an easy method to verify the purity of a function.

Learning Journal

Local Functions

How do you implement private methods in a functional programming language.

Learning Journal

Statements and Expressions

Statements and Expressions in Scala. How are they different?

Learning Journal

Lazy Evaluations

Evaluate the expression now vs evaluate it for the first use. Strict vs Lazy?

Learning Journal