Laziness with Clojure

It took me a long time to fully grasp the concept of laziness and eagerness despite working with Clojure for the past few months. As a result, I decided to try and concisely explain the concepts here for any other beginners and also in an attempt to consolidate my understanding. A huge thanks to the book The Joy of Clojure* by Michael Fogus and Chris House. It provided the clearest explanation thus far (for me) on the topic and will be the basis for this post along with a few other sources.

First, what is laziness? Briefly put, it is an evaluation strategy where expressions are evaluated only when their values are needed. I know, that makes little sense at the moment, but bear with me. Second, why should we care? To quote Fogus and House, “the fundamental reason for laziness in Clojure [is] the avoidance of full realization of interim results.” Take that line in, recognize the powerful implications it has. We’ll discuss more after the break.

So you must have question – what is an evaluation strategy? Why is it good or bad to evaluate expressions as they are needed? All good questions. This blog won’t go into the theory behind evaluation strategy, but suffice to say evaluation strategy is the means that a programming language evaluates expressions. In essence, it is how the programming language runs functions or methods or whatever you would like to call it. Lazy evaluation has functions evaluated only when they are actually needed. So what do I mean by this? Let’s take a brief Java example with the following snippet:

if (moo != null && moo.isCow()) {
...
}

Here we have an object moo that must both exist and be a cow.  However, recognize that if moo is null, then calling moo.isCow() would return an exception since there is no moo object to begin with! The && java logical operator is, in that sense, lazy. What it does is it is evaluated if, and only if, the if statement is still true. Whenever the statement is false, the expression returns since there is no need to test whether the next statement is true or not. Furthermore, in the above code it allows in built error prevention by ensuring that moo must exist in order to see if moo is a cow.

With this example we already see a benefit with laziness – the ability to prevent exceptions being thrown at us when handling potentially null values or functions. Furthermore, as you may have guessed by this point, the ability to evaluated functions on an as needed basis allows for performance improvements and, theoretically, infinitely looping functions to exist.  In essence, you can have theoretically infinite sequences in Clojure, just see this snippet which creates all numbers from 1 to infinity.

(iterate inc 1)

That being said, Clojure is not lazy in all instance by default (unlike some other languages such as Haskell), meaning that non tail-end recursion can lead to exceptions being thrown unless you are careful to ensure laziness. Thankfully Clojure has built in features to help you be more lazy! I’ll leave these links here for you to read at your own time on that note.

It’s not all roses and candy. There are instances where you do not want to use laziness – when you want to be eager. Common occurrences of this are when you want to pass through an evaluated sequence using map. Once again, thankfully, Clojure has features to solve our problems there too – this link is a good starting point.

Hopefully this post has given you a simple foundation with which to build your understanding of lazy and eager programming and how Clojure has the capacity to handle both. Cheers!

 

* Note: This post uses Amazon Affiliate links. If you would like to click the non-affiliate link for The Joy of Clojure click this link.

 

Leave a comment