In my latest book called “Lazy Programmers”, I cover the “good” lazy techniques, the “bad” lazy techniques, and the ugly ramifications of not knowing the difference. The full chapter of “good” lazy techniques is posted on my website. Chief among those techniques is how often the word “lazy” is used in a programmer’s everyday vernacular for things like “lazy loading”, “lazy evaluation”, “lazy initialization”, “lazy instantiation” and many more! While the examples in the book are written in Java, there are many other languages that use these techniques in equal or greater measure. Since I am learning Haskell to do some DaPP programming on the Cardano blockchain, I am amazed at just how important lazy evaluation is in that language. In fact, though still a newbie in learning the language, I am confident that Haskell has taken lazy initialization to a new level. Let’s examine lazy initialization in Haskell via two simple programs.
Haskell uses lazy initialization as a default position and not just an option. A good way to demonstrate that is to look at infinite lists in Haskell. Yes, infinite lists are legal and pretty cool. Let’s first look at a trivial example and then follow up with an example with a bit more substance. Listing 1 is a trivial example of Haskell only initializing what it needs and only when it needs it.
Listing 1 Infinite.hs
— simple program using infinite lists
printSubList :: Int -> [Int] -> IO ()
printSubList 0 list = putStr(“ “)
printSubList n list = do
let x = take 1 list :: [Int]
putStr (show (x!!0))
putStr “ “
let m = n — 1 :: Int
printSubList m (tail list)
main = do
let infiniteList = [1..] :: [Int]
— Below line hangs forever trying to get the length of an infinite list!
— putStrLn $ “Length of infinite list is: “ ++ show (length infiniteList)
let subList = take 5 infiniteList
putStrLn $ “Length of sublist is: “ ++ show (length subList)
— use the infinite list in a function
printSubList 5 infiniteList
This Haskell program has two functions: main and printSubList. So, we begin with main function and see that we declare an infinite list (called “infiniteList”). You see how Haskell uses the “..” notation to separate the list range and if you don’t specify an upper bound, Haskell let’s you declare an infinite list. Haskell will not actually populate that list unless you try and do something with it (like the commented out line that would have tried to execute the length function on an infinite list which would do the right thing and run forever)! The second function printSubList is just to show how you can pass that infinite list into a function and do something with it. In this case, all we do is print each member using recursion and then exit. Now let’s examine a Haskell Program that is a bit more useful as in Listing 2 below:
Listing 2 sieve.hs
sieve :: [Int] -> [Int]
sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
main = do
let nPrimes = take 10 $ sieve [2..] :: [Int]
putStrLn $ “10 Prime Numbers: “ ++ show nPrimes
Again the sieve program has two functions and is a simple implementation of the “Sieve of Eratosthenes” which was an ancient way of generating prime numbers. In this example, we pass an infinite list to the sieve function which uses something called “list comprehension” (a form of list generation) to generate an infinite list of prime numbers but the “take” function lazily only requests 10 of them.
Why is this important?
It is a powerful argument on the “pro-lazy” side of the debate that I examine in detail in “Lazy Programmers”. I will be discussing and expanding upon each part of the book as I post new chapters to my website. If you want to join the discussion, visit my forum on Quora. See you there!