% File: kassert.pl % Author: Peter Clark % Purpose: Implementation of an assertn-like predicate in Prolog (!) % Date: revised Feb 1992, tidied June 1992 :- ensure_loaded([utils]). /* Implementation of an assert-like db, but also supporting an assertn-like pred. NOTE: ----- 1. It is not necessary to understand this code in order to understand LPE. All you need to know is that mentioned under "the kassert database" in lpe.doc. 2. For completeness, this file also defines a few extra predicates not used by LPE. Layout of the kassert.pl: ------------------------- This file contains two `layers' 1. Implementation of doubly-linked lists in Prolog 2. Predicates defining a kassert database, analagous to Prolog's assert database, but supporting kassertn as well as kasserta and kassertz. This uses the doubly-linked lists. See kassert.doc for detailed documentation. % ====================================================================== % LAYER ONE: DOUBLY-LINKED LISTS IN PROLOG % ====================================================================== Predicates: new_chain(+StartKey) Create a new `anchor' link with key Key start_chain(+StartKey) Same, but no error if anchor already exists follow_chain(+StartKey, -Key, -Data). Look for Data from StartKey on insert_before(+Key, +Term) insert Term before the Term with Key insert_after(+Key, +Term) insert Term after the Term with Key link_data(?Key, ?Data) find the Data with key Key delete_link(+Key, ?Data) delete Data with key Key from the list show_chain_and_keys(+StartKey) write out the entire linked list show_chain(+StartKey) same, but just display the data items delete_chain(+StartKey) delete the chain starting with StartKey */ :- dynamic link/4. % new_chain(+StartKey). % Creates the initial `anchor' link for a chain indexed by StartKey new_chain(StartKey) :- link(StartKey, _, _, _), !, write('ERROR! Start key '),write(StartKey),write(' is already used!'), nl, fail. new_chain(StartKey) :- gensym(k, NextKey), assertz( link(StartKey,nil,NextKey,start) ), !. % -------------------- % start_chain(+StartKey). % Checks that a chain anchored by StartKey exists, and if not creates one start_chain(StartKey) :- link(StartKey, _, _, _), !. start_chain(StartKey) :- gensym(k, NextKey), assertz( link(StartKey,nil,NextKey,start) ), !. % -------------------- % follow_chain(+StartKey, -Key, -Data) % The main predicate for accessing the linked list data. % Follow the chain from StartKey, returning the Data and Key of items % encountered. On backtracking, look further down the chain. follow_chain(StartKey, FoundKey, Data) :- link(StartKey, _, NextKey, _), follow_chain2(NextKey, FoundKey, Data). follow_chain2(Key, FoundKey, FoundData) :- link(Key, _, NextKey, Data), ( FoundKey = Key, FoundData = Data ; follow_chain2(NextKey, FoundKey, FoundData) ). % ====================================================================== % INSERTION OF NEW ITEMS IN THE MIDDLE OF THE LINKED LIST % ====================================================================== /* insert_before(+Key, +Term) insert Term before the Term with Key insert_after(+Key, +Term) insert Term after the Term with Key eg. insert_before(k2, newd) } equivalent insert_after( k1, newd) } NOTE: Rather than simply assigning a new key to the new data, we are a bit more sophisticated: the new data *steals* the key KNext of the data D immediately following the insertion point, and a new key is created for D instead. This is done to preserve the link from KPrev to KNext, "remembered" by follow_chain/3, so that follow_chain/3 has correct behaviour on backtracking. OLD LINKED LIST NEW LINKED LIST k1-d1, k2-d2, k3-d3, k4-d4 => k1-d1, k2-newd, newk-d2, k3-d3, k4-d4 */ insert_after(Key, Data) :- link(Key, _, NKey, _), !, insert_between(Key, NKey, Data). insert_after(Key, _) :- write('ERROR! No such link '), write(Key), write('!'), nl, fail. insert_before(NKey, Data) :- link(Key, _, NKey, _), !, insert_between(Key, NKey, Data). insert_before(NKey, _) :- write('ERROR! No predecessor of link '), write(NKey), write('!'), nl, fail. % below P=previous, N=next, eg. above PKey,NKey,NNKey,NNNKey = k1,k2,k3,k4 insert_between(PKey, NKey, Data) :- gensym(k, NewKey), ( retract( link(NKey,PKey,NNKey,NData) ) -> % maybe change NextKey.. assertz( link(NewKey,NKey,NNKey,NData) ), ( retract( link(NNKey,NKey,NNNKey,NNData) ) -> % and NextNextKey assertz( link(NNKey,NewKey,NNNKey,NNData) ) ; true ) ; true ), assertz( link(NKey,PKey,NewKey,Data) ). % -------------------- % link_data(?Key, ?Data). Find the Data with key Key link_data(Key, Data) :- link(Key, _, _, Data). % -------------------- % delete_link(+Key, ?Data) % Delete the data Data with key Key in the linked list delete_link(Key, Data) :- retract( link(Key,PrevKey,NextKey,Data) ), !, retract( link(PrevKey,PrevPrevKey, Key,PrevData) ), assertz( link(PrevKey,PrevPrevKey,NextKey,PrevData) ), ( retract( link(NextKey, Key,NextNextKey,NextData) ) -> assertz( link(NextKey,PrevKey,NextNextKey,NextData) ) ; true ). delete_link(Key, _) :- write('ERROR! No such link '), write(Key), write(' to delete!'), nl, fail. % -------------------- delete_chain(StartKey) :- retract( link(StartKey,_,NextKey,_) ), !, delete_chain(NextKey). delete_chain(_). % -------------------- Display routines -------------------- % show_chain_and_keys(+StartKey) show_chain_and_keys(StartKey) :- follow_chain(StartKey, Key, Data), write(Key), write(' '), pretty_write(Data), write('.'), nl, fail. show_chain_and_keys(_). show_chain(StartKey) :- follow_chain(StartKey, _, Data), pretty_write(Data), write('.'), nl, fail. show_chain(_). % ====================================================================== % LAYER TWO: DEFINITIONS FOR THE KASSERT DATABASE % ====================================================================== /* ======================================== kcall(+Term) % call Term kcall(+Term, -Key) % call Term, returning Key of succeeding clause kclause(?Head, ?Body) % look up clausen in db kclause(?Head, ?Body, ?Key) % look up clausen with key Key in db kclause(?Head, ?Body, +StartKey, -Key) %look up clausen, starting from (but % *not* including clause at) StartKey next_kclause(+Key, -NextKey) % get key of clause following clause with Key kassertm(+Clause, +Key) % assert Clause before the clause with Key kassertn(+Clause, +Key) % assert Clause after the clause with Key kasserta(+Clause) % assert Clause at start of k-db kretract(+Clause). % retract Clause (Clause can contain vars) kretract(?Clause, +Key) % retract the Clause with Key klisting(Funct/Ar) % list clauses for Goal of given functor/arity kabolish(Funct/Ar) % delete all clauses for Goal of functor/arity ======================================== */ % (Find out the Key of clause then retract it) kretract((H:-B)) :- !, kclause(H, B, Key), kretract((H:-B), Key). kretract( H ) :- !, kclause(H, true, Key), kretract( H , Key). kretract((H:-B), Key) :- !, delete_link(Key, (H:- B)). kretract( H , Key) :- delete_link(Key, (H:-true)). kassertm((H:-B), Key) :- !, insert_before(Key, (H:- B)). kassertm( H , Key) :- insert_before(Key, (H:-true)). kassertn((H:-B), Key) :- !, insert_after(Key, (H:- B)). kassertn( H , Key) :- insert_after(Key, (H:-true)). % (Find the startkey of clauses for H, then insert the clause at the start) kasserta((H:-B)):- !, kstartkey(H, Key), start_chain(Key), insert_after(Key, (H:- B)). kasserta( H ):- kstartkey(H, Key), start_chain(Key), insert_after(Key, (H:-true)). kcall(Term) :- kcall(Term, _). kcall(Term, Key) :- kclause(Term, Body, Key), call(Body). kclause(Head, Body) :- kclause(Head, Body, _). % [1]: directly look up link with Key % [2]: follow the chain, returning each Head-Body-Key triple on backtracking kclause(Head, Body, Key) :- nonvar(Key), !, link_data(Key, (Head:-Body)). %[1] kclause(Head, Body, Key) :- kstartkey(Head, StartKey), %[2] kclause(Head, Body, StartKey, Key). kclause(Head, Body, StartKey, Key) :- follow_chain(StartKey,Key,(Head:-Body)). next_kclause(Key, NextKey) :- link(Key, _, NextKey, _). % kstartkey(+Head, -Key): % Generate key to index the start of clauses for Head. Here the Key is % the functor and arity of Head. % This can be user re-defined if it suits him/her. (Quite useful for CSPs) kstartkey(Head, F/A) :- functor(Head, F, A). klisting(Key) :- show_chain(Key). klisting_and_keys(Key) :- show_chain_and_keys(Key). kabolish(Key) :- delete_chain(Key), start_chain(Key). % -------------------- kassert_demo :- demo_goals([ kabolish(f/1), kasserta(f(a)), kasserta(f(b)), klisting(f/1), kcall(f(X)) - [X/'X'] - allsolns, kcall(f(X),Key) - [X/'X',Key/'Key'] - allsolns, kclause(f(X),Body,Key) - [X/'X',Body/'Body',Key/'Key'] - allsolns, klisting(f/1), ( kclause(f(b),true,Key), kassertn(f(c),Key) ), klisting(f/1), ( kclause(f(c),true,Key2), kassertn((f(d):-f(e)),Key2) ), klisting(f/1), kretract(f(b)), klisting(f/1), ( kclause(f(d),_,Key4), kretract((f(c):-Body)), kassertn((f(c):-Body),Key4) ), klisting(f/1) ]), write('End of demo!!'), nl, nl.