As per several comments, the language doesn't make many guarantees about the order of evaluation of subexpressions in a program like:
main = print $ div 100 0 + 20 * 100
So, div 100 0
could be evaluated first and throw an error before 20 * 100
is evaluated, or vice versa. Or, the whole expression could be optimized into unconditionally throwing a division by zero error without evaluating anything, which is what actually happens if you compile it with ghc -O2
.
In actual fact, at least with GHC 8.6.5, the function:
foo :: Int -> Int -> Int -> Int
foo x y z = div x y + z * x
compiled with ghc -O2
produces code that attempts the division first and will throw an error if y == 0
before attempting the multiplication, so the subexpressions are evaluated in the order they appear.
HOWEVER, the function with the opposite order:
bar :: Int -> Int -> Int -> Int
bar x y z = z * x + div x y
compiled with ghc -O2
ALSO produces code that tries the division first and will throw an error if y == 0
before attempting the multiplication, so the subexpressions are evaluated in reverse order.
Moreover, even though both versions try the division before the multiplication, there's still a difference in their evaluation order -- bar
fully evaluates z
before trying the division, while foo
evaluates the division before fully evaluating z
, so if a lazy, error-generating value is passed for z
, these two functions will produce different behavior. In particular,
main = print $ foo 1 0 (error "not so fast")
throws a division by zero error while:
main = print $ bar 1 0 (error "not so fast")
says "not so fast". Neither attempts the multiplication, though.
There aren't any simple rules here. The only way to see these differences is to compile with flags that dump intermediate compiler output, like:
ghc -ddump-stg -dsuppress-all -dsuppress-uniques -fforce-recomp -O2 Test.hs
and inspect the generated code.
If you want to guarantee a particular evaluation order, you need to write something like:
import Control.Parallel (pseq)
foo' :: Int -> Int -> Int -> Int
foo' x y z = let a = div x y
b = z * x
in a `pseq` b `pseq` a + b
bar' :: Int -> Int -> Int -> Int
bar' x y z = let a = z * x
b = div x y
in a `pseq` b `pseq` a + b
The function pseq
is similar to the seq
function discussed in the comments. The seq
function would work here but doesn't always guarantee an evaluation order. The pseq
function is supposed to provide a guaranteed order.
If your actual goal is to understand Haskell's lazy evaluation, rather than prevent specific subexpressions from being evaluated in case of errors in other subexpressions, then I'm not sure that looking at these examples will help much. Instead, taking a look at this answer to a related question already linked in the comments may give you better sense of how laziness "works" conceptually.