/*  ASSERT_N.PL  */
 
 
:- module assert_n.
 
 
:- public assert_n/2,
          retract_n/2,
          clause_n/3.
 
 
/*
SPECIFICATION
-------------
 
These are some predicates for random access to the database that I
experimented with when writing the Tutor. The current version does
not use them.         
 
 
PUBLIC assert_n( Clause, N+ ):
 
Asserts Clause after clause N for the same predicate. If there are not N
clauses for this predicate, then Clause is asserted at the end of the
database.
 
Clause must be sufficiently instantiated to determine its predicate; N
must be instantiated.
 
 
PUBLIC retract_n( Clause, N+ ):
 
Retracts the Nth clause for Clause's predicate. If there are not N
clauses, or if Clause doesn't match the Nth clause, retract_n fails and
does not change the database.
 
The arguments are as for assert_n.
 
 
PUBLIC clause_n( Head, Body?, N+ ):
 
"Head:-Body is the Nth clause of Head's predicate".
 
Head must be sufficiently instantiated to determine which predicate it
belongs to. N must be instantiated.
 
The predicate fails if N is out of range (=< 0 or >= no. of clauses), or
if clause N didn't match Head:-Body.
*/
 
 
/*
IMPLEMENTATION
--------------
*/
 
 
/*
assert_n
--------
 
To implement assert_n( Clause, N ), we first retract the first N-1 (or
all of them if there are fewer than N-1) clauses for Clause's predicate.
This is done by retract_n( Predicate, Arity, I, Clauses ) which
instantiates Clauses to a list (in reverse order) of the retracted
clauses. We then asserta(Clause), and finally call asserta for each of
the retracted clauses.
*/
 
 
assert_n( Clause, N ) :-
    clause_vs_pred_arity( Clause, P, A ),
    retract_n( P, A, N, Clauses ),
    asserta( Clause ),
    asserta_all( Clauses ).
 
 
/*
retract_n
---------
 
To implement retract_n( Clause, N ), we first test that the Nth
clause exists and matches Clause, by calling clause_n. If it doesn't,
we fail.
 
Otherwise, we retract the first N-1 clauses as for assert_n, then
retract the Nth, and finally re-assert the N-1.
*/
 
 
retract_n( Clause, N ) :-
    clause_vs_head_tail( Clause, Head, Tail ),
    clause_n( Head, Tail, N ),
    !,
    N_less_1 is N-1,
    clause_vs_pred_arity( Clause, P, A ),
    retract_n( P, A, N_less_1, Clauses ),
    retract( Clause ),
    asserta_all( Clauses ).
 
 
/*  retract_n( P+, A+, N+, Clauses- ):
        Retract the first N clauses for P/A (or all of them if fewer
        than N), instantiating Clauses to a list of them, in reverse
        order.
*/
retract_n( P, A, N, Clauses ) :-
    retract_n( P, A, N, [], Clauses ).
 
 
retract_n( P, A, 0, Clauses, Clauses ) :- !.
 
retract_n( P, A, N, Clauses0, Clauses ) :-
    functor( Head, P, A ),
    (
        retract( Head:-Tail )
    ->
        N_ is N - 1,
        retract_n( P, A, N_, [Head:-Tail|Clauses0], Clauses )
    ;
        Clauses = Clauses0
    ).
 
 
/*  asserta_all( List+ ):
        List is a list of clauses. asserta each element. This puts the
        clauses into the database in reverse order, so that List's first
        element is at the end.
*/
asserta_all( [] ) :- !.
 
asserta_all( [Clause|Rest] ) :-
    asserta( Clause ),
    asserta_all( Rest ).
 
 
/*
clause_n.
---------
 
This can't be done without some form of assignment that's preserved over
backtracking. I repeatedly backtrack over 'clause' and use a counter to
record the number of backtracks. The counter is initialised by
    set_counter( Name, Value )
and incremented by
    increment_counter( Name, NewValue )
which unifies NewValue with the new value. When it's finally finished
with, all record of it is cleared from the database by
    clear_counter( Name ).
 
It is good practice to make sure we always call 'clear_counter'; but
this does limit the modes in which 'clause_n' can be used, as shown
below.
 
For the counter to be any use, both arguments to 'clause' must be as
uninstantiated as possible. If one of them were to be a bit instantiated
and it failed to match the clause being examined, 'clause' would
backtrack to the next matching clause without updating the counter,
which would then get out of sync.
 
We want to ensure that if 'clause_n' succeeds, it leaves no trace of its
counter behind. To do this we call 'clear'. To guarantee that this
happens, even if the Nth clause fails to match Head:-Body, Head (the
original head) and NewHead (the copy which is instantiated on each call
of 'clause') must not be matched until _after_ the counter is cleared
from the database. The same goes for Tail and NewTail.
 
We can't write a version of 'clause_n' that's used as an enumerator of
clauses, i.e. one which on each resatisfaction returns the next clause
and the next value of N, unless we're willing to tolerate leaving an
uncleared counter behind. This is because of the possibility of calls
like
    ?-clause_n( Head, Tail, N ), !.
where the cut after the first solution makes it impossible for
'clause_n' to clear the counter by eventually backtracking into its
second clause.
*/
 
 
clause_n( Head, Body, N ) :-
    set_counter( i, 0 ),
    functor( Head, P, A ),
    functor( NewHead, P, A ),
    clause( NewHead, NewBody ),
        increment_counter( i, I ),
        (
            I = N
        ->
            clear_counter(i),
            NewHead = Head,
            NewBody = Body
        ;
            fail
        ),
    !.
 
clause_n( _, _, _ ) :-
    clear_counter(i),
    fail.
 
 
/*  set_counter( Name+, Val+ ):
        Set counter Name to Val.
*/
set_counter( Counter, Val ) :-
    retractall( val(Counter,_) ),
    asserta( val(Counter,Val) ).
 
 
/*  increment_counter( Name+, Val? ):
        Increment counter Name and unify Val with its new value.
*/
increment_counter( Counter, NewValOut ) :-
    retract( val(Counter,OldVal) ),
    !,
    NewVal is OldVal + 1,
    asserta( val(Counter,NewVal) ),
    NewValOut = NewVal.
 
 
/*  clear_counter( Name+ ):
        Remove all references to counter Name.      
*/
clear_counter( Counter ) :-
    retractall( val(Counter,_) ).
 
 
:- endmodule.
