Arc Forumnew | comments | leaders | submitlogin
Why can't I find anyone else who's also disappointed in arc's lack of any backtrace ability?
8 points by Thr4wn 5832 days ago | 13 comments
when I searched arclanguage.org and google for any information about how this, I couldn't find anything, so I assume that arc doesn't have that ability. Pretty much my favorite thing in CL was that I could view a backtrace whenever an error occured. Is there any way to do something like that in arc?


7 points by almkglor 5832 days ago | link

Nope. After all, pg made Arc for hackers, who never make mistakes, and can write an Arc interpreter on bare metal by flipping switches in time to the processor's 100MHz bus clock.

This sounds like a good thing to add to the arc2c compiler anyway ^^. Certainly we could get a backtrace reasonably easily by actually inspecting the closure structures being passed as continuations, but we also have to determine a decent function-to-symbol mapping.

-----

4 points by bOR_ 5832 days ago | link

it would be appreciated! :P. Functional programming helps because usually it is the last thing you wrote that is wrong, but occasionally I'm completely puzzled by error messages (only to find out that one of my comments started with a : rather than a ;.

-----

4 points by almkglor 5832 days ago | link

Hmm. Anyway it looks like it might be useful to subtype function closures into continuation and non-continuation functions (as an aside it would probably be useful also for optimizations: when a continuation function exits, it can't be called and its closure can be immediately freed or reused, unless we use 'ccc: and even so we could just copy the continuation into a non-continuation closure).

Then when a backtrace is requested we simply scan through the stack for continuation-type functions, and scan through their closures for continuation-types, and so on until we end up on a closure without any continuation-type functions.

-----

2 points by stefano 5831 days ago | link

While scanning the stack you have to pay attention to not include functional arguments as if they were called functions. To give descriptive names to functions I would transform every lambda expression in a named function, e.g. :

  (def f (x) x) --> (set f (fn (x) x)) --> (set f (__named '(f x) (x) x))
and for anonymous functions:

  (fn (a b) (+ a b)) --> (__named '(fn (a b)) (a b) (+ a b))

-----

1 point by almkglor 5831 days ago | link

> While scanning the stack you have to pay attention to not include functional arguments as if they were called functions.

Which is why I was proposing to subtype closures into continuations and non-continuations. Normal functions that are passed around are non-continuations, while continuation closures are created during CPS-conversion. Of course we probably need to add code in 'ccc which would probably copy a continuation closure into a non-continuation version of the closure.

-----

3 points by conanite 5831 days ago | link

Two things I've wondered about backtraces:

1. With tail-call optimisation, don't some stack frames just completely disappear, so by default there's no way for an interpreter to list them in a backtrace? I might have completely misunderstood TCO, of course. And if we redefined 'def so that it inserts code to build a stack trace, would this not cause the kind of memory leak that TCO is designed to eliminate, unless the stack-trace builder detects and excludes recursive calls?

2. Backtraces are even nicer with source file and line number information. Maybe I'm spoilt, having grown up in javaland. But with macro-expansion and quasiquotation and so on, how can an interpreter tell what file/line number a particular expression comes from? And as a user (an arc user, that is) trying to make sense of a back trace (obviously not a real hacker, as noted by almkglor above), is it more useful to know the source of the macro into which my erroneous expression expanded, or to know the pre-expansion source of the problem?

I haven't a clue what other lisps do for backtraces.

-----

3 points by almkglor 5831 days ago | link

1. Yes. This is actually good. You don't want a 1000-iteration loop cluttering a backtrace do you?

Without TCO:

  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
  in function gs42
     ....985 more times....
  in function foo
  in function bar
  from top level
With TCO:

  in function gs42
  in function foo
  in function bar
  from top level
Less is more?

That said, if you're implementing a state machine using tail-called functions, you lose the trace of the state and can only get the current state (arguably a good thing too - you don't want to have to hack through 600 state transitions, either). You'll probably have to dump the state on-screen or on-disk somehow instead.

2. Yes, this is difficult. IIRC some Schemes actually internally use an abstract syntax tree (not lists) and make macros work on that tree, not on lists. The abstract syntax tree includes the file and line number information.

The problem here is: what do you end up debugging, the macro or the code that uses the macro? If the macro-expansion is a complicated expression, then the bug might actually be in the macro, with the original code quite correct.

In arc2c the file and line number info are lost pretty early. Maybe we could use an AST direct from the file read-in, and silently fool macros into thinking that the AST is a list.

-----

2 points by stefano 5830 days ago | link

1. I've thought about this too and then looked how it worked in SBCL, and I've seen that it doesn't show tail optimized functions in the stack backtrace. I've developed a few programs with SBCL and this has never been a problem.

-----

2 points by Darmani 5831 days ago | link

If we had first class special forms, one might have some success with something like this:

  (= *call-stack* (list))

  (let old-fn fn
    (mac fn (args . body)
      (w/uniq (name ret)
        `(,old-fn ,args
            (push (list ,name ,body) *call-stack*)
             (let ,ret (do ,@body)
              (pop  *call-stack*)
               ,ret)))))
From there, just hack the REPL to include an on-error around everything. Perhaps some more complex hacking could be done to insert code that would allow one to track where in a function one before an error occurs.

Since we don't have first-class special forms, in the meantime, one can just replace this redefinition of fn with a similarly-named macro and then replace all invocations of fn to this macro. Should probably wrap this around the primitives too.

Of course, I'm not advocating this as ideal; I'm just exploring the possibility of doing this in pure Arc (or almost pure; I forget whether the REPL is in Arc or Scheme). Aside from seed issues, I'm sure this setup has many problems. Is it possible to get this kind of thing to work with multiple threads?

-----

3 points by almkglor 5831 days ago | link

For threads: Anarki has thread-local variables:

  (= call-stack* (thread-local))
  (= (call-stack*) (list))

  (let old-fn fn
    (mac fn (args . body)
      (w/uniq (name ret)
        `(,old-fn ,args
            (push (list ,name ,body) (call-stack*))
             (let ,ret (do ,@body)
              (pop  (call-stack*))
               ,ret)))))
Note the extra layer of parenthesization.

-----

5 points by eds 5831 days ago | link

http://arclanguage.org/item?id=4070

In pg's last poll, less opaque error messages were voted one the most desired features.

That said, we still haven't seen any hint that a new release is coming any time soon.

-----

3 points by Thr4wn 5832 days ago | link

Has anyone else found some way to have a makeshift backtrace? I've thought of a couple ideas that _might_ work...

Is there any way to at least (by function call) preform a break, because whenever mzscheme recieves a SIGINT, it does provide at least a basic backtrace. If there is a way to call a break, then I think I could wrap averything around an 'on-err that would cause a break (not to be confused with causing a system _to_ break :) ).

Or is there any way to return an ID for the running thread? because maybe I could use 'break-thread. (or maybe for debugging I'll always just run my functions inside a new thread so that I can break them by function call, but this is starting to sound like an annoying hack)

-----

2 points by almkglor 5832 days ago | link

None yet. You could hack on ac.scm on Anarki to add the function to get the current thread ID.

-----