This is really the result of laziness.
Laziness means that simply defining a value does not mean that it will be appreciated; this will only happen if necessary. If it is not needed, the code that would actually produce it is not "do anything." If a specific value is required, the code is launched, but only the first time it is needed; if there are other references to the same value, and it is used again, these uses will directly use the value that was created the first time.
You must remember that functions are meanings in every sense of the term; everything related to ordinary values also applies to functions. Thus, your definition of f simply writes the expression for the value, the evaluation of the expression will be deferred until the value of f is needed, and since the value (function) is twice as large, the calculation calculations will be saved and reused a second time.
Let's take a closer look at this:
f=trace("f was called")$(+1)
You determine the value of f using a simple equation (without using any syntactic sugar to write the arguments on the left side of the equation or provide cases through several equations). Therefore, we can simply take the right side as one expression that defines the value of f . Just determining that he is not doing anything, he sits there until you call:
print $ f 1
Now print needs to evaluate its argument, so this forces the expression f 1 . But we cannot apply f to 1 without first forcing f . Therefore, we need to find out what function the trace "f was called" $ (+1) expression performs. Thus, trace is actually called if its insecure IO and f was called prints to the terminal, and then trace returns its second argument: (+1) .
So now we know what function f : (+1) . f will now be a direct reference to this function, without having to evaluate the source code of trace("f was called")$(+1) if f is called again. This is why the second print does nothing.
This case is completely different, although it may look similar:
fn=trace("f was called:"++show n)$n+1
Here we use syntactic sugar to define functions, writing down the arguments on the left. Let desugar to denote lambda to more clearly see what the actual value associated with f :
f = \n -> trace ("f was called:" ++ show n) $ n + 1
Here we wrote the value of the function directly, not an expression that can be evaluated to call the function. Therefore, when f needs to be estimated before it can be called by 1 , the value of f is an entire function; the trace call is inside the function instead of being the thing called to execute the function. Thus, trace not called part of the evaluation of f , it is called as part of the evaluation of the application f 1 . If you saved the result of this (for example, by running let x = f 1 ) and then printing it several times, you will see only one trace. But when we come to evaluate f 2 , the call to trace still exists inside the function, which is the value of f , so when f is called again, so this is trace .