It would be absolutely correct to wrap every value in a thunk. But since Haskell is non-strict, compilers can choose when to evaluate thunks/expressions. In particular, compilers can choose to evaluate an expression earlier than strictly necessary, if it results in better code.
Optimizing Haskell compilers (GHC) perform Strictness analysis to figure out, which values will always be computed.
In the beginning, the compiler has to assume, that none of a function's arguments are ever used. Then it goes over the body of the function and tries to find functions applications that 1) are known to be strict in (at least some of) their arguments and 2) always have to be evaluated to compute the function's result.
In your example, we have the function (+)
that is strict in both it's arguments. Thus the compiler knows that both x
and y
are always required to be evaluated at this point.
Now it just so happens, that the expression x+y
is always necessary to compute the function's result, therefore the compiler can store the information that the function add
is strict in both x
and y
.
The generated code for add
* will thus expect integer values as parameters and not thunks. The algorithm becomes much more complicated when recursion is involved (a fixed point problem), but the basic idea remains the same.
Another example:
mkList x y =
if x then y : []
else []
This function will take x
in evaluated form (as a boolean) and y
as a thunk. The expression x
needs to be evaluated in every possible execution path through mkList
, thus we can have the caller evaluate it. The expression y
, on the other hand, is never used in any function application that is strict in it's arguments. The cons-function :
never looks at y
it just stores it in a list. Thus y
needs to be passed as a thunk in order to satisfy the lazy Haskell semantics.
mkList False undefined -- absolutely legal
*: add
is of course polymorphic and the exact type of x
and y
depends on the instantiation.
result = add 2 + 4
should beresult = add 2 4
. – Excise