Question

I understand what lazy evaluation is, and how it works and the advantages it has, but could you explain me what strict evaluation really is in Haskell? I can't seem to find much info about it, since lazy evaluation is the most known.

What are the benefit of each of them over the other. When is strict evaluation actually used?

Was it helpful?

Solution

Strictness happens in a few ways in Haskell,

First, a definition. A function is strict if and only if when its argument a doesn't terminate, neither does f a. Nonstrict (sometimes called lazy) is just the opposite of this.

You can be strict in an argument, either using pattern matching

-- strict
foo True   = 1
foo False  = 1

-- vs
foo _ = 1

Since we don't need to evaluate the argument, we could pass something like foo (let x = x in x) and it'd still just return 1. With the first one however, the function needs to see what value the input is so it can run the appropriate branch, thus it is strict.

If we can't pattern match for whatever reason, then we can use a magic function called seq :: a -> b -> b. seq basically stipulates that whenever it is evaluated, it will evaluated a to what's called weak head normal form.

You may wonder why it's worth it. Let's consider a case study, foldl vs foldl'. foldl is lazy in it's accumulator so it's implemented something like

 foldl :: (a -> b -> a) -> a -> [b] -> a
 foldl f accum []     = acuum
 foldl f accum (x:xs) = foldl (f accum x) xs

Notice that since we're never strict in accum, we'll build up a huge series of thunks, f (f (f (f (f (f ... accum)))..)))

Not a happy prospect since this will lead to memory issues, indeed

*> foldl (+) 0 [1..500000000]
     *** error: stack overflow

Now what'd be better is if we forced evaluation at each step, using seq

 foldl' :: (a -> b -> a) -> a -> [b] -> a
 foldl' f accum []     = accum
 foldl' f accum (x:xs) = let accum' = f accum x
                         in accum' `seq` foldl' f accum' xs

Now we force the evaluation of accum at each step making it much faster. This will make foldl' run in constant space and not stackoverflow like foldl.

Now seq only evaluates it values to weak head normal form, sometimes we want them to be evaluated fully, to normal form. For that we can use a library/type class

 import Control.DeepSeq -- a library on hackage

  deepseq :: NFData a => a -> b -> a

This forces a to be fully evaluated so,

 *> [1, 2, error "Explode"] `seq` 1
    1
 *> [1, 2, error "Explode"] `deepseq` 1
    error: Explode
 *> undefined `seq` 1
  error: undefined
 *> undefined `deepseq` 1
  error undefined

So this fully evaluates its arguments. This is very useful for parallel programming for example, where you want to fully evaluate something on one core before it's sent back to the main thread, otherwise you'd just create a thunk and all the actual computation would still be sequential.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top