Idea for SSU debugging feature - skip the guard statements at the start

Speaking of =>/2, may I suggest a debugging feature? Consider this, well, pseudocode:

a(X),
    X > 1,
    X > 2,
    X > 3
 => writeln(first).

a(X),
    X < -1,
    X < -2,
    X < -3
 => writeln(second).

a(_)
 => writeln(defaulty).

If I enter trace mode, all the guards are processed.

[trace]  ?- a(-2.5).
   Call: (10) a(-2.5) ? creep
   Call: (11) -2.5>1 ? creep
   Fail: (11) -2.5>1 ? creep
   Redo: (10) a(-2.5) ? creep
   Call: (11) -2.5< -1 ? creep
   Exit: (11) -2.5< -1 ? creep
   Call: (11) -2.5< -2 ? creep
   Exit: (11) -2.5< -2 ? creep
   Call: (11) -2.5< -3 ? creep
   Fail: (11) -2.5< -3 ? creep
   Redo: (10) a(-2.5) ? creep
   Call: (11) writeln(defaulty) ? creep
defaulty
   Exit: (11) writeln(defaulty) ? creep
   Exit: (10) a(-2.5) ? creep
true.

Would it make sense to restrict the trace (optionally of course) to the bodies only, basically like this:

[trace]  ?- a(-2.5).
   Call: (10) a(-2.5) ? creep
   Call: (11) writeln(defaulty) ? creep
defaulty
   Exit: (11) writeln(defaulty) ? creep
   Exit: (10) a(-2.5) ? creep
true.

I am asking this because the guards are typically rather simple, less error-prone, than the bodies. At least in my instances.

Interesting idea. Not sure how easy it is and you probably want this as an option.

Unsure if feasible, probably depending on whether => is just syntactic sugar or a different implementation. IIRC you wrote that it is the latter.

I hesitate to develop this idea further since the real work would probably done by you :blush:

But if you can point me to the two places in the code (where is the =>, where is the "skip), I am willing to try a patch.

If there is a guard, the implementation is basically

  Head ?=> Guard, !, Body.

The ?=> operator from picat, which performs SSU but does not commit is not public, but is internally used to deal with guards. I think the trick would be to extend the trace condition code and use the program counter to find that we are in the guard. You have the clause. A flag will tell you it is an SSU rule. Next you need to step over the VM code to find that the current PC is between the SSU neck and subsequent !. That does not help if the guard is not a simple built-in, but a user defined predicate that calls other predicates. One option to that could be to use the silent skip only for system predicates. Doable, but some work … If I get a PR, great. Otherwise it just goes on the stack :slight_smile:

I also find the idea interesting.

First off, I have not used SSU to any great extent but do use gtrace almost daily.

If I understand your proposal, a trace (not gtrace) output would skip all of the guard statements for an SSU.

Would this also change gtrace?

If it would change gtrace then instead of skipping by default, consider adding a new control flow command to skip the guards.

Also as one who uses prolog_trace_interception/4, how would that change?

As this is getting off topic, I have no problem moving the replies to a new topic, let me know if that is desired.

A new topic might be wise.

It all depends how you implement this. The tracer runs on calls from the VM (Virtual machine). When in debug mode it always makes these calls. The trace function first does some filtering, e.g., are we in debug or trace mode, when in debug mode, did we hit a spy point? When in trace mode, are we “skipping”, etc. If that all is positive it calls prolog_trace_interception/4, which drives also the graphical debugger. If that fails it uses the C defined command line debugger.

My suggesting was be to add hiding the guards to conditions in the trace filtering. An alternative might be to “move” the unify port. This port is an addition to the normal port model. It fires after the head unification. It is used by the gui tracer, such that stepping into a call first shows the clause whose head unified. We could change the VM such that for guarded SSU rules the unify code fires at the ! that ends the guard by introducing a new instruction that simply uses the cut,
and then triggers the unify port. That would make @mgondan1’s idea work for the guitracer, but not for the conventional tracer.

As you point out, a command to skip all the guards could be a serious alternative.

1 Like

Kinda split a “skip” into two steps.

a,
    writeln(guard1),
    writeln(guard2)
 => writeln(commit1),
    writeln(commit2).

Current skip (fine):

[trace]  ?- a.
   Call: (10) a ? skip
guard1
guard2
commit1
commit2
   Exit: (10) a ? creep
true.

Current creep (also fine)

[trace]  ?- a.
   Call: (10) a ? creep
   Call: (11) writeln(guard1) ? skip
guard1
   Exit: (11) writeln(guard1) ? creep
   Call: (11) writeln(guard2) ? skip
guard2
   Exit: (11) writeln(guard2) ? creep
   Call: (11) writeln(commit1) ? skip
commit1
   Exit: (11) writeln(commit1) ? creep
   Call: (11) writeln(commit2) ? skip
commit2
   Exit: (11) writeln(commit2) ? creep
   Exit: (10) a ? creep
true.
[trace]  ?- 

“uppercase C” or whatever letter is suitable

[trace]  ?- a.
   Call: (10) a ? CREEP
guard1
guard2
   Call: (11) writeln(commit1) ? skip
commit1
   Exit: (11) writeln(commit1) ? creep
   Call: (11) writeln(commit2) ? skip
commit2
   Exit: (11) writeln(commit2) ? creep
   Exit: (10) a ? creep
true.
[trace]  ?- 

It would boil down to the following code:

notrace, a,
    writeln(guard1),
    writeln(guard2)
 => trace,
    writeln(commit1),
    writeln(commit2).

So that I am wondering if this couldn’t be made completely from “outside” the WAM, with macros such as goal_expansion/2. Or do I miss something?

Its not correct because if the guard fails the debugger is still off. Besides that, the deal with SWI-Prolog has always been that code did not have to be prepared to be able to debug. I’d rather keep it that way unless we get something big in return. The simplest solution is probably one of the two I suggested: play around with the location of the unify port or filter the guard calls away in the trace function condition.

Of course, now I see the obvious, thanks.