Two function calls, but only one trace displayed

With GHC version 8.0.2, the following program:

import Debug.Trace f=trace("f was called")$(+1) main = do print $ f 1 print $ f 2 

outputs:

 f was called 2 3 

Is this expected behavior? If so, why? I expected the string f was called be printed twice, one before 2 and one before 3 .

Same result on TIO: Try it online!

EDIT But this program:

 import Debug.Trace fn=trace("f was called:"++show n)$n+1 main = do print $ f 1 print $ f 2 

outputs:

 f was called:1 2 f was called:2 3 

Try it now!

I suspect that this behavior has something to do with laziness, but my questions remain: is this the expected behavior and, if so, why?

Hackage states the following:

The trace function displays the trace message given as the first argument before returning the second argument as the result.

I do not see it in the first example.

EDIT 2 Third example based on @amalloy comments:

 import Debug.Trace fn=trace "f was called"$n+1 main = do print $ f 1 print $ f 2 

outputs:

 f was called 2 f was called 3 
+5
source share
2 answers

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 .

+2
source

Tracing is done when f defined, not when called. If you want the trace to run as part of the call, you must make sure that it is not evaluated before receiving the parameter:

 fx = trace "f was called" $ x + 1 

Also, when I launch my TIO, I don’t see what the trace looks like at all. trace is actually not a reliable way to print because it tricks the input / output model on which the language is built. The smallest changes in the evaluation order may disrupt it. Of course, you can use it for debugging, but as this simple example shows, it does not guarantee much.

In your editing, you provide trace documentation:

The trace function displays the trace message given as the first argument before returning the second argument as the result.

And indeed, this is exactly what is happening in your program! When defining f ,

 trace "f was called" $ (+ 1) 

must be appreciated. First printed "f was called." Then trace evaluates and returns (+ 1) . This is the final value of the trace expression, and therefore (+ 1) is what f is defined as. trace disappeared, see?

+4
source

Source: https://habr.com/ru/post/1271372/


All Articles