28

I need some clarification about laziness with Haskell.

If I have this function:

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
     where
            a = ...
            b = ...
            c = ...
            d = ...

When I call myFunction 1, Haskell will evaluate only the a = ... function, neither b nor c, nor d.

But if I write

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
     where
            (a,b,c,d) = anotherFunction arg

What will Haskell's behaviour be?

  • Will it evaluate only a and 'propagate' the lazyness to anotherFunction?
  • Or, will it evaluate the whole tuple (a,b,c,d) as result of anotherFunction?
senshin
  • 10,022
  • 7
  • 46
  • 59
JeanJouX
  • 2,555
  • 1
  • 25
  • 37
  • 1
    It will evaluate the tuple `x = anotherFunction arg`, but not all _elements_ of the tuple. – Zeta Mar 31 '15 at 10:06
  • 2
    When you say "call `myFunction 1`" I assume you mean when that expression is evaluated. As Zeta says, it will evaluate the tuple (but not the elements) and then evaluate `a` from that tuple. – augustss Mar 31 '15 at 10:10
  • @Zeta Is it right to say that the other values of the tuples `b`,`c` and `d` will be in thunk form. Or is there a more proper word for that? – Sibi Mar 31 '15 at 10:15
  • @Sibi: I guess it's even more proper to say that the tuple will be in *weak head normal form*, but thunks are ok. – Zeta Mar 31 '15 at 10:19
  • @Zeta Thanks, updated it appropriately. – Sibi Mar 31 '15 at 11:01

4 Answers4

30

In both cases, it won't evaluate anything unless the value is demanded. One way of demanding the value is calling the function in ghci (which prints the value in ghci and hence demanding it). Assuming that you are executing the function, then in your second case it will evaluate the tuple to weak head normal form (WHNF) and then evaluate the first element in (a,b,c,d) because only that value is demanded. The other elements b, c and d will be in the thunk form. In fact you can verify that yourself:

myFunction arg
  | arg == 1 = a
  | arg == 2 = a*b
  | arg == 3 = b+c
  | otherwise = (a+b)*c
  where
    (a,b,c,d) = anotherFunction arg

anotherFunction x = (x, undefined, undefined, undefined)

Demo in ghci:

λ> myFunction 1
1
Community
  • 1
  • 1
Sibi
  • 47,472
  • 16
  • 95
  • 163
  • 7
    @JeanJouX undefineds are a very effective of testing non-strictness in a language. – PyRulez Mar 31 '15 at 21:36
  • 3
    @thefourtheye Iff something works when one of the inputs is undefined, it means the program never tried evaluating it. – PyRulez Apr 03 '15 at 16:46
14

Well it is only interested in a, so that means that there is an implicit function:

thea :: (a,b,c,d) -> a
thea (a,_,_,_) = a

In other words Haskell is not interested in the other elements of the tuple. Sometimes however the elements of the tuple share some structure. Say another function is defined as:

anotherFunction :: Int -> (Int,Int,Int,Int)
anotherFunction x = (z,t,f,g)
    where f = x*x
          g = f+x
          z = g-2
          t = 3

In that case - in order to evaluate the first element - the third and fourth element will also be evaluated. But since you don't do anything with them, Haskell won't be interested in their result specifically.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
12

As others have already pointed out, only a will be evaluated.

Keep however in mind that to exploit laziness it is crucial that anotherFunction returns a tuple before evaluating its components. For example, consider

anotherFunction n = if p > 1000 then (n, p) else (n, 0)
  where p = product [1..n]

The above will always evaluate product [1..n], even if the caller only demands the first pair component (which is n). This is because the if needs to be evaluated before the pair can be returned, and that forces p. By contrast,

anotherFunction n = (n, if p > 1000 then p else 0)
  where p = product [1..n]

will immediately return the pair. If only its first component is evaluated, then p will not be computed at all.

chi
  • 111,837
  • 3
  • 133
  • 218
7

Unless there is a need to get the value of that variable, it will not evaluated. Basically Haskell is so lazy unless it is told not to be so.

You can confirm this, like this

Prelude> :set +m
Prelude> let anotherFunction = (100, 1 `div` 0)
Prelude| 
Prelude> let myFunction arg
Prelude|                | arg == 1  = a
Prelude|                | otherwise = b
Prelude|                where
Prelude|                     (a, b) = anotherFunction
Prelude| 

Here, 1 `div` 0 will raise divide by zero error. If it evaluates all the elements, then even when you invoke myFunction with 1, you would have gotten that error, but

Prelude> myFunction 1
100

only when you invoke it with any other value, there is a need to evaluate the second value of the tuple, and it will fail with divide by zero error.

Prelude> myFunction 2
*** Exception: divide by zero
thefourtheye
  • 233,700
  • 52
  • 457
  • 497