3

I have a knowledge base consisting of a set of rules whose head of each rule performs assert or retract of complex terms when certain conditions occur.

How can I ensure that Id is incremented with each assert(term(Id,A,B,C))?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • Yes i would sequential – user3062889 Jan 28 '19 at 20:53
  • What about `retract`? How should the resulting "holes" be dealt with? – repeat Jan 28 '19 at 22:13
  • Of interest: SWI-Prolog [Delimited continuations](http://www.swi-prolog.org/pldoc/man?section=delcont) – Guy Coder Jan 29 '19 at 15:26
  • @GuyCoder. (1) I don't see the tag "identity" as helpful. (2) "sequentially" is superfluous. "incremented" means "+1"; not "+2"; not "+1.5". – repeat Jan 29 '19 at 17:09
  • @GuyCoder. I do see some merit in you adding "sequentially", though. It's a bit of a wording problem... multiple increments are done in sequence, but I don't think that misunderstandings are likely. – repeat Jan 29 '19 at 17:13

2 Answers2

5

Assuming you don't care about holes in the Id (which occur when retracting id_person/2 clauses) you could do:

:- dynamic nextID/1.
:- dynamic id_person/2.
nextID(0).

assertz_person(P) :-
   nextID(I),
   retract(nextID(I)),
   I1 is I+1,
   assertz(nextID(I1)),
   assertz(id_person(I,P)).

Sample use (works with SWI-Prolog 8.0.0 and SICStus Prolog 4.5.0):

?- id_person(I,P).
false.

?- assertz_person(joan), id_person(I,P).
I = 0, P = joan.

?- assertz_person(al), assertz_person(ian), id_person(I,P).
   I = 0, P = joan
;  I = 1, P = al
;  I = 2, P = ian.
repeat
  • 18,496
  • 4
  • 54
  • 166
  • 1
    `assert/1` is legacy; `assertz/1` (or `asserta/1`) is standard. Also, you can avoid the if-then-else construct by using an `initialization/1` directive to initialize the `current_id /1` predicate. – Paulo Moura Jan 28 '19 at 22:36
  • @PauloMoura. Right! Old habits:) Is the initialization directive (in this case) any better than a simple fact? (It is an additional concept.) – repeat Jan 28 '19 at 22:54
  • 3
    I don't think you need both `nextID(I), retract(nextID(I))`, I think `retract(nextID(I))` is sufficient. – Daniel Lyons Jan 28 '19 at 23:15
  • @DanielLyons. Good idea. But the SICStus manual (https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dmdb_002drcd_002defu.html) got me a bit uneasy, will check that out tomorrow. Thanks! – repeat Jan 28 '19 at 23:23
  • 1
    It would be problematic _if_ `nextID/1` were nondeterministic, but absent threads it should only ever have one solution. If you have threads, then you have other problems anyway (you need a critical section or a lock). – Daniel Lyons Jan 28 '19 at 23:35
  • @DanielLyons. You're right. It works. And a bit faster, too. – repeat Jan 29 '19 at 17:07
  • @repeat I'm going to have that comment framed – Daniel Lyons Jan 29 '19 at 17:33
5

As you're asserting clauses for a term/3 predicate where the first argument is a unique (integer) identifier, there's no need for an auxiliary dynamic predicate to represent the current counter. You can simply do instead:

:- dynamic(term/3).

assert_term(A, B, C) :-
    (   term(Id, _, _, _) ->
        NextId is Id + 1
    ;   NextId is 1
    ),
    asserta(term(NextId, A, B, C)).

The call to asserta/1 will make the latest asserted clause for term/3 to be the first to be retrieved when called, as above, with all arguments unbound, thus providing access to the last count. This solution assumes, however, that the clauses are not being retracted arbitrarily.

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33