Non-strictness in Haskell

I always marvel at the elegance of the Haskell language, especially how trivially easy it is to construct non-strict (which is not quite the same as lazy) values and just let the runtime handle everything for you.

For example, undefined is the bottom type (for readers for familiar with OO, it is akin to an Exception in C/Java) in Haskell. Evaluating undefined in GHCI throws an exception:

A bit of background for those who are not familiar with GHCI: GHCI is the read-print-eval loop (REPL) interactive program for developers to test out a small snippet of Haskell code. It is quite common in functional languages. The `λ>` prompt indicates where you would type in your Haskell code for evaluation.

Configuring GHCi with the following packages:
GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from C:\Users\rexcf\.ghci
Loaded GHCi configuration from C:\Users\rexcf\AppData\Local\Temp\haskell-stack-ghci\2a3bbd58\ghci-script
λ> undefined
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries\base\GHC\Err.hs:79:14 in base:GHC.Err
undefined, called at <interactive>:1:1 in interactive:Ghci1

However, putting undefined in a list is perfectly fine:

λ> let a = [1,2,3,undefined]
λ>

As long as a is not evaluated, GHCI is perfectly happy to accept an undefined in a form that does not require evaluation (more precisely reduction) yet. In the example above, since no code requires a to be enumerated to the fourth element, the Haskell compiler does not throw an error.

You can even take the first three elements from a without problem:

λ> take 3 a
[1,2,3]

GHCI will only throw an error when you try to get to the fourth element:

λ> take 4 a
[1,2,3,*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries\base\GHC\Err.hs:79:14 in base:GHC.Err
undefined, called at <interactive>:7:17 in interactive:Ghci1

This property is known as non-strict (sometimes also misquoted as lazy).

This section is more of a reminder to myself. Let’s use another example of an infinite natural number sequence. Here is how simple the code is:

λ> let z = [1..] :: [Int]
λ>

The :: [Int] part is only necessary for GHCI to infer z as an integer list instead of a list of a more polymorphic Num a => a number typeclass.

Just to prove this is really an infinite sequence, we can try to evaluate it:

λ> z
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,^cInterrupted

Oops, it is really an infinite sequence. I terminated the evaluation so that my PC does not run indefinitely.

But how can I know how much of z has been evaluated if the other parts of my program simply use only a few elements of z?

Since our last session crashed, let’s try again. This time will only take five elements from z:

λ> let z = [1..] :: [Int]
λ> take 5 z
[1,2,3,4,5]

All is good. Now, since we forced a partial evaluation of z, it means that the Haskell runtime must have already evaluated the first five elements of z right? How can we check that?

Enter the command :sprint (Simplified print, :sp for short)

λ> :sp z
z = 1 : 2 : 3 : 4 : 5 : _

GHCI clearly shows that the first five elements of z has been evaluated (with their values) and that the rest of z is still a thunk.

Let’s also evaluate the sixth element of z. Haskell lists are also zero-index based so we can use !! 5 to get the sixth element.

λ> z !! 5
6
λ> :sp z
z = 1 : 2 : 3 : 4 : 5 : 6 : _

Notice how the second :sp call prints an extra element of z because we just forced the evaluation of z to get the sixth element.

To conclude, given Haskell’s non-strict nature, sometimes it is useful for programmers to understand whether a sub-expression has been reduced or not. :sprint is a useful GHCI command for understanding how non-strictness works in Haskell.

Programmer | Watch enthusiast

Programmer | Watch enthusiast