First an example run to make sure it does what you expect.
Start SWI-Prolog to get Toplevel.
Welcome to SWI-Prolog (threaded, 64 bits, version 8.1.12)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?-
Since all of the code for this is in a single directory to make it easier to demonstrate,
change the working directory to the location of the files. Obviously the directory name in the following example needs to be changed to what you are using.
?- working_directory(_, 'C:/Users/Eric/Documents/Projects/Prolog/Persistence example/').
true.
Load the first program and run it
?- consult("program_a.pl").
true.
?- program_1.
Starting saving
Done
true.
?-
At this point the facts are still in file buffer but not written to the file. To get them saved to a file halt/0 must be run. Running halt/0 will close the Toplevel
Doing this will create a file that contains the saved facts.
file: data.journal
created(1569346573.8356802).
assert(entity("a")).
assert(entity("b")).
When I first saw the word assert
in the file it confused me, but AFAIK that is how it works.
Start SWI-Prolog to get to Topelvel
Welcome to SWI-Prolog (threaded, 64 bits, version 8.1.12)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?-
Since Toplevel
was restarted, need to change the directory again.
?- working_directory(_, 'C:/Users/Eric/Documents/Projects/Prolog/Persistence example/').
true.
Load the second program and run it.
?- consult("program_b.pl").
true.
?- program_2.
Starting reading program
"a"
"b"
false.
?-
Details
program_a.pl
:- use_module('program_database.pl').
program_1 :-
format('Starting saving ~n',[]),
A = "a",
B = "b",
% Stuff
%assert(entity(A)),
(
exists_entity(A)
;
add_entity(A)
),
%assert(entity(B)),
(
exists_entity(B)
;
add_entity(B)
),
% More stuff
format('Done ~n',[]).
program_b.pl
:- use_module('program_database.pl').
program_2 :-
format('Starting reading program ~n',[]),
% Stuff
write_entities.
write_entities :-
facts:entity(A),
write_canonical(A),nl,
fail.
The module that implements the predicates exists_entity/1
and add_entity/1
program_database.pl
:- module(
facts,
[
add_entity/1, % +Identifier:string
exists_entity/1 % ?Identifier:string
]
).
:- use_module(library(persistency)).
:- persistent
entity(identifier:string).
:- initialization(db_attach('data.journal', [])).
exists_entity(Identifier):-
with_mutex(facts_journal, entity(Identifier)).
add_entity(Identifier):-
with_mutex(facts_journal, assert_entity(Identifier)).
Now to explain it all.
The key to all of this is creating the module facts, (program_database.pl
) which uses library(persistency)
.
The example you gave was as simple as it can get, it is only one fact entity/1
with one parameter and no need to use retract.
The first item needed is access to the persistency
library
:- use_module(library(persistency)).
The next item that the persistence store needs is a term/spec.
In library(persistency) it states:
persistent +Spec
Declare dynamic database terms. Declarations appear in a directive and have the following format:
:- persistent
<callable>,
<callable>,
...
Each specification is a callable term, following the conventions of library(record), where each argument is of the form
name:type
Types are defined by library(error).
The type
parameter is found in must_be/2 but note that there are more values than listed in the table and list above the table. The real list is in the code as has_type/2
Also note that there can be more than one term/spec associated with a persistence store, but for this example there is only the one, entity
.
For this example the type string
will be used.
:- persistent
entity(identifier:string).
The next item that the persistence store needs is some predicates to query/read and assert/write the term/spec.
In library(persistency) it states:
The persistent/1 expands each declaration into four predicates:
name(Arg, ...)
assert_name(Arg, ...)
retract_name(Arg, ...)
retractall_name(Arg, ...)
These four predicates are created by library(persistence) for each spec, e.g. entity(identifier:string)
and how user code interacts with the persistence file. However for this case only the first two are needed.
name(Arg, ...)
- queries/reads the facts from the persistence store/file
assert_name(Arg, ...)
- adds facts to the persistence store/file
To ensure no access collisions, with_mutex/2 is used.
exists_entity(Identifier) :-
with_mutex(facts_journal, entity(Identifier)).
add_entity(Identifier):-
with_mutex(facts_journal, assert_entity(Identifier)).
Now with exists_entity/1
and add_entity/1
user code can query/read and assert/write to the persistence store/file/journal.
The next item that the persistence store needs is a file name.
In library(persistency) it states:
db_attach (:File, +Options)
Use File as persistent database for the calling module. The calling module must defined persistent/1 to declare the database terms. Defined options:
sync (+Sync)
One of
close
(close journal after write),
flush
(default, flush journal after write) or
none
(handle as fully buffered stream).
If File is already attached this operation may change the sync behavior.
:- initialization(db_attach('data.journal', [])).
The last item is to convert the code in to a module and export the predicates.
:- module(
facts,
[
add_entity/1, % +Identifier:string
exists_entity/1 % ?Identifier:string
]
).
The first program creates the data to be persisted makes use of the facts
module
:- use_module('program_database.pl').
These predicates work in combination to ensure no value is duplicated in the persistence store, and is stored if it doesn’t exist.
(
exists_entity(A)
;
add_entity(A)
)
halt/0
is run to close the file so that the buffer is flushed. I am sure there is a better way, but I have not needed it so have not looked.
The second program reads the persisted data makes use of the facts
module
:- use_module('program_database.pl').
It only uses a failure driven loop as there can be more than one fact of the same functor.
facts:entity(A)
If the line is changed to
exists_entity(A)
it only returns “a” and not “b”. I don’t know why (probably with_mutex/2). If anyone knows the fix for that I am interested to learn.
Hopefully this gives you the details you need to expand into what you need. As I noted, there is room for improvement and any feedback is appreciated.
Ask questions if this does not make sense anywhere.