Some development environments provide a tracer, which allows you to
observe the entering and exiting of function calls
[1].
E.g. in Common Lisp you can do:
(defun flatten (list)
(cond
((null list) list)
((atom list) (list list))
(t (append (flatten (first list))
(flatten (rest list))))))
(trace flatten)
(flatten '(a (b c) d))
which could output
0 FLATTEN > ...
>> LIST : (A (B C) D)
1 FLATTEN > ...
>> LIST : A
1 FLATTEN < ...
<< VALUE-0 : (A)
1 FLATTEN > ...
>> LIST : ((B C) D)
2 FLATTEN > ...
>> LIST : (B C)
3 FLATTEN > ...
>> LIST : B
3 FLATTEN < ...
<< VALUE-0 : (B)
3 FLATTEN > ...
>> LIST : (C)
4 FLATTEN > ...
>> LIST : C
4 FLATTEN < ...
<< VALUE-0 : (C)
4 FLATTEN > ...
>> LIST : NIL
4 FLATTEN < ...
<< VALUE-0 : NIL
3 FLATTEN < ...
<< VALUE-0 : (C)
2 FLATTEN < ...
<< VALUE-0 : (B C)
2 FLATTEN > ...
>> LIST : (D)
3 FLATTEN > ...
>> LIST : D
3 FLATTEN < ...
<< VALUE-0 : (D)
3 FLATTEN > ...
>> LIST : NIL
3 FLATTEN < ...
<< VALUE-0 : NIL
2 FLATTEN < ...
<< VALUE-0 : (D)
1 FLATTEN < ...
<< VALUE-0 : (B C D)
0 FLATTEN < ...
<< VALUE-0 : (A B C D)
(A B C D)
As you can see, the macro TRACE
only enables printing the
trace. Printing a trace without having a way to access it
directly/programmatically seems rather premature to me. It is evident
from the above trace that traces form a recursive tree-like data type, that however is not accessible to the programmer.
The entering and the exiting values from a function belong to one node of the
tree (even though they are printed far apart), and the subtraces are
subtrees.
Thus, continuing the above example, such a `trace' data structure
might roughly resemble
(FLATTEN (A (B C) D) (A B C D)
(FLATTEN A (A))
(FLATTEN ((B C) D) (B C D)
(FLATTEN (B C) (B C)
(FLATTEN B (B))
(FLATTEN (C) (C)
(FLATTEN C (C)) ; X
(FLATTEN NIL NIL)))
(FLATTEN (D) (D)
(FLATTEN D (D))
(FLATTEN NIL NIL))))
Since I could not find anything about this, I was wondering the
following: are there any languages (or easy ways of implementing it
yourself) that can reify traces as data structures such as above
that you can manipulate? In case you are wondering, there are
potentially many reasons why this might be useful. E.g. one might like
to compare two traces, or store a trace for later, or even consider the
trace itself as a kind of higher-level program (although I have not
investigated this idea in depth).
Note that I am not referring to `stack traces' (a.k.a. `backtraces'). The
stack trace at the execution of location X above might be a structure
resembling
(FLATTEN (A (B C) D)
(FLATTEN ((B C) D)
(FLATTEN (B C)
(FLATTEN (C)
(FLATTEN C))))) ; X
Debuggers usually already provide many ways of manipulating
the stack trace after e.g. a exception or breakpoint (although I do not know
which ones are able to reify it...). Also, as I recall, one
simple way of implementing continuations is copying the stack (which is
part of the continuation) to the heap. So stack traces are not the
issue here, nor are the `traces' as the word is sometimes used in
theoretical computer science (sequences of successive states of some
state transition system).
[1]
It is actually an interesting question what it would mean to trace
operators other than functions, but I did not go into that here.
Recent comments
2 weeks 5 days ago
43 weeks 24 min ago
43 weeks 4 hours ago
43 weeks 4 hours ago
1 year 13 weeks ago
1 year 17 weeks ago
1 year 18 weeks ago
1 year 18 weeks ago
1 year 21 weeks ago
1 year 26 weeks ago