As a result of lazy evaluation, the code in each of lines 7-9 is only run if the value of the corresponding binding is evaluated/used in the course of evaluation of code for the case that matches. So:
- If case 1 is true, none of lines 7-9 are run.
- If case 1 is false but case 2 is true, then evaluation of
newStatus runs line 7, but lines 8-9 are not run.
- If cases 1 and 2 are false but case 3 is true, then evaluation of
newStatus2 runs line 8 which evaluates newStatus2Temp causing line 9 to run. Line 7 is not run.
The where clauses themselves can only be attached to entire pattern bindings (e.g., the whole f3 ([p1,p2]:tail) status | ... | ... = ... expression), not individual guards, so a guard can't have its own where clause. You could either repeat the pattern for each guard:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status | status !! (p1-1) == 0 = f3 tail status
f3 ([p1,p2]:tail) status | status !! (p2-1) == 1 = f3 tail newStatus1
where newStatus1 = set status p1 0
f3 ([p1,p2]:tail) status | otherwise = f3 tail newStatus2
where newStatus2 = set newStatus2Temp p1 1
newStatus2Temp = set status p2 0
or use let ... in ... blocks:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
| status !! (p1-1) == 0 = f3 tail status
| status !! (p2-1) == 1
= let newStatus1 = set status p1 0
in f3 tail newStatus1
| otherwise
= let newStatus2 = set newStatus2Temp p1 1
newStatus2Temp = set status p2 0
in f3 tail newStatus2
I don't think there's anything wrong with your where-clause version, and it's not unusual to write Haskell code where only a subset of the bindings in the where-clause are used (or even valid/meaningful) for each case. With such small helpers, this specific example might be more clearly written without any helpers though:
f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
| status !! (p1-1) == 0 = f3 tail $ status
| status !! (p2-1) == 1 = f3 tail $ set status p1 0
| otherwise = f3 tail $ set (set status p2 0) p1 1
With GHC and -O2, all four of these (your original code and these three variants) compile to identical low-level code, so use whichever you think is clearest.