Controlling eagerness in lazy languages In lazy programming languages such as Haskell, although the default is to evaluate expressions only when they are demanded, it is possible in some cases to make code more eager—or conversely, to make it more lazy again after it has been made more eager. This can be done by explicitly coding something which forces evaluation (which may make the code more eager) or avoiding such code (which may make the code more lazy).
Strict evaluation usually implies eagerness, but they are technically different concepts. However, there is an optimisation implemented in some compilers called
strictness analysis, which, in some cases, allows the compiler to infer that a value will always be used. In such cases, this may render the programmer's choice of whether to force that particular value or not, irrelevant, because strictness analysis will force
strict evaluation. In Haskell, marking
constructor fields strict means that their values will always be demanded immediately. The seq function can also be used to demand a value immediately and then pass it on, which is useful if a constructor field should generally be lazy. However, neither of these techniques implements
recursive strictness—for that, a function called deepSeq was invented. Also,
pattern matching in Haskell 98 is strict by default, so the
~ qualifier has to be used to make it lazy.
Simulating laziness in eager languages C++ In
C++, the std::ranges
library uses lazy range adaptors. import std; using std::vector; using std::ranges::to; using std::views::filter; using std::views::iota; using std::views::transform; vector v = iota(1, 1'000'000) // lazily generate ints from 1 to 1,000,000 | filter([](int x) -> bool { return x % 2 == 0; }) // filter out odd numbers, keeping evens | transform([](int x) -> int { return x * x; }) // square each value | to(); // convert the range back to a vector
Java In
Java, lazy evaluation can be done by using objects that have a method to evaluate them when the value is needed. The body of this method must contain the code required to perform this evaluation. Since the introduction of
lambda expressions in Java SE8, Java has supported a compact notation for this. The following example
generic interface provides a framework for lazy evaluation: interface Lazy { T eval(); } The Lazy interface with its eval() method is equivalent to the Supplier interface with its get() method in the java.util.function library. Each class that implements the Lazy interface must provide an eval method, and instances of the class may carry whatever values the method needs to accomplish lazy evaluation. For example, consider the following code to lazily compute and print 210: Lazy a = () -> 1; for (int i = 0; i b = a; a = () -> b.eval() + b.eval(); } System.out.println("a = " + a.eval()); In the above, the variable initially refers to a lazy
integer object created by the lambda expression () -> 1. Evaluating this lambda expression is similar to constructing a new instance of an
anonymous class that implements Lazy with an method returning . Each iteration of the loop links to a new object created by evaluating the lambda expression inside the loop. Each of these objects holds a reference to another lazy object, , and has an method that calls b.eval() twice and returns the sum. The variable is needed here to meet Java's requirement that variables referenced from within a lambda expression be effectively final. This is an inefficient program because this implementation of lazy integers does not
memoize the result of previous calls to . It also involves considerable
autoboxing and unboxing. What may not be obvious is that, at the end of the loop, the program has constructed a
linked list of 11 objects and that all of the actual additions involved in computing the result are done in response to the call to a.eval() on the final line of code. This call
recursively traverses the list to perform the necessary additions. We can build a Java class that memoizes a lazy object as follows: function to initialize
Delegated properties with the clause like so: var flipped = true val isThePizzaReady : Boolean by lazy { println("I'll check if the pizza is ready right now.") // if this runs more than once then it will output false. flipped = !flipped !flipped } fun main() { println("Is my pizza ready?") assert (isThePizzaReady) // does not crash println("is it still ready though?") assert(isThePizzaReady) // still doesn't crash } You can run this example yourself by going to the Kotlin Playground.
Verification To verify that the program lazily loaded instead of initializing it beforehand as true we need to check the output of the program. Is my pizza ready? I'll check if the pizza is ready right now. is it still ready though? As we can see when the program reads from it prints out a statement we put inside the lazy code block on
line 4. We can also verify that it did not run the lazy block twice and reused the previous output of it via the fact that: • The program did not throw an exception, the assert function will throw an if the output of is false. which can only happen if the lazy block is executed more than once. • The statement on
line 4 does not execute more than once.
Python In
Python 2.x the range() function computes a list of integers. The entire list is stored in memory when the first assignment statement is evaluated, so this is an example of eager or immediate evaluation: r = range(10) print r • prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print r[3] • prints 3 In Python 3.x the range() function returns a
generator which computes elements of the list on demand. Elements are only generated when they are needed (e.g., when print(r[3]) is evaluated in the following example), so this is an example of lazy or deferred evaluation: r: Iterator[int] = range(10) print(r) • prints range(0, 10) print(r[3]) • prints 3 :This change to lazy evaluation saves execution time for large ranges which may never be fully referenced and memory usage for large ranges where only one or a few elements are needed at any time. In Python 2.x is possible to use a function called xrange() which returns an object that generates the numbers in the range on demand. The advantage of xrange is that generated object will always take the same amount of memory. r = xrange(10) print r • prints xrange(10) lst = [x for x in r] print lst • prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] From version 2.2 forward, Python manifests lazy evaluation by implementing iterators (lazy sequences) unlike tuple or list sequences. For instance (Python 2): numbers: Iterator[int] = range(10) iterator: Iterator[int] = iter(numbers) print(numbers) • prints [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(iterator) • prints print(iterator.next()) • prints 0 :The above example shows that lists are evaluated when called, but in case of
iterator, the first element '0' is printed when need arises.
.NET In the
.NET framework, it is possible to do lazy evaluation using the class System.Lazy. The class can be easily exploited in
F# using the lazy keyword, while the force method will force the evaluation. There are also specialized collections like Microsoft.FSharp.Collections.Seq that provide built-in support for lazy evaluation. let fibonacci = Seq.unfold (fun (x, y) -> Some(x, (y, x + y))) (0I,1I) fibonacci |> Seq.nth 1000 In C# and VB.NET, the class System.Lazy is directly used. public int Sum() { int a = 0; int b = 0; Lazy x = new(() => a + b); a = 3; b = 5; return x.Value; // returns 8 } Or with a more practical example: // recursive calculation of the n'th fibonacci number public int Fib(int n) { return (n == 1) ? 1 : (n == 2) ? 1 : Fib(n - 1) + Fib(n - 2); } public void Main() { Console.WriteLine("Which Fibonacci number do you want to calculate?"); int n = Int32.Parse(Console.ReadLine()); Lazy fib = new(() => Fib(n)); // function is prepared, but not executed bool execute; if (n > 100) { Console.WriteLine("This can take some time. Do you really want to calculate this large number? [y/n]"); execute = (Console.ReadLine() == "y"); } else { execute = true; } if (execute) { Console.WriteLine(fib.Value); // number is only calculated if needed } } Another way is to use the yield keyword: // eager evaluation public IEnumerable Fibonacci(int x) { IList fibs = new List(); int prev = -1; int next = 1; for (int i = 0; i LazyFibonacci(int x) { int prev = -1; int next = 1; for (int i = 0; i ==See also==