Retracting and asserting to another file in Prolog - prolog

I'm trying to retract and assert a fact in another file. One (fruit1.pl) contains a couple of facts, and another (fruit.pl) contains a predicate start which designates which fact that another predicate insert_fruit will update:
fruit1.pl
fruit(apple, [[2, yellow], [1, brown]]).
fruit(orange, [[3, orange], [2, orange]]).
fruit.pl
:- dynamic fruit/2.
start :-
consult('fruit1.pl'),
File = 'fruit1.pl',
Name = apple,
Price = 2,
Color = red,
insert_fruit(File, Name, Price, Color).
insert_fruit(File, Name, Price, Color) :-
open(File, update, Stream),
retract(fruit(Name, Information)),
assert(fruit(Name, [[Price, Color]|Information])),
close(Stream).
However insert_fruit is not working as intended, as I believe it needs to include Stream to modify the other file, although I have no idea how (retract(Stream, ...) doesn't work). Is there some I would be able to get the retract and assert predicates to function in the other file?

In SWI-Prolog you can assert/retract facts from a file that is used as a persistent fact store by using library persistency:
You declare fruit/3 as persistent. Optionally: you annotate the arguments with a type for automatic type checking.
You attach a file that will serve as the persistent fact store upon initialization of the fruit module (in this case fruit1.pl).
You add predicates for inserting (i.e., add_fruit/3) and querying (i.e., current_fruit/3) fruity facts. Retraction is handled similarly.
Notice that you can use the fact store in a multi-threaded environment by using with_mutex/2 (especially useful when you start retracting facts as well).
Code
:- module(
fruit,
[
add_fruit/3, % +Name:atom, +Price:float, +Color:atom
current_fruit/3 % ?Name:atom, ?Price:float, ?Color:atom
]
).
:- use_module(library(persistency)).
:- persistent(fruit(name:atom, price:float, color:atom)).
:- initialization(db_attach('fruit1.pl', [])).
add_fruit(Name, Price, Color):-
with_mutex(fruit_db, assert_fruit(Name, Price, Color)).
current_fruit(Name, Price, Color):-
with_mutex(fruit_db, fruit(Name, Price, Color)).
Illustration of use
Start Prolog, load fruit.pl, execute:
?- add_fruit(apple, 1.10, red).
Close Prolog, start Prolog (again), execute:
?- current_fruit(X, Y, Z).
X = apple,
Y = 1.1,
Z = red
You are now reading facts from fruit1.pl!
Illustration of automatic type checking
As mentioned before, the library also performs type checking for you, e.g.:
?- add_fruit(pear, expensive, green).
ERROR: Type error: `float' expected, found `expensive' (an atom)

Related

Prolog: How to read data from console and store into database. Getting errors

update :-
write("Name?:"),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
write("Continue(y or n)?:"),
read(Respond),
process(Respond).
process(y) :-
write('Name?:'),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
repeat,
write("y or n"),
read(Respond),
process(Respond).
process(n) :- !.
I want to run this Prolog to assert in the name and age, but when I write age for the number, it shows
?- update.
Name?:fred.
Age?:|: 25.
ERROR: Type error: `callable' expected, found `25' (an integer)
ERROR: In:
ERROR: [9] assert(25)
ERROR: [8] update at c:/example.pl:11
ERROR: [7] <user>
?-
How to figure out this problem.
Problem 1
Incorrect input for assert/1
The problem is not with just Age it is with any input that uses assert, e.g.
?- update.
Name?:Fred
|: .
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [9] assert(_4940)
ERROR: [8] update at c:/example.pl:8
ERROR: [7] <user>
?- update.
Name?:Jim.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR: [9] assert(_5826)
ERROR: [8] update at c:/example.pl:8
ERROR: [7] <user>
The problem is that assert/1 is not being given a fact or rule.
assert/1 says:
Assert a clause (fact or rule) into the database.
See facts and rules
In the example above Fred is not a fact because it does not end with a period (.).
In the example above with Jim. a period was given but because Jim starts with a capital letter, it is not a fact or rule but a variable.
When the age is entered as a number, again this is not a fact or rule it is an integer.
Problem 2
Use of read/1 which says:
Read the next Prolog term from the current input stream and unify it with Term.
When reading a Prolog term the input must end with a period.
This not only requires the input to be a term, but end with a . which is even more confusing given the prompt, e.g Age. Most of the examples you find do what you did, the corrected code below does what you want.
Problem 3
Competing ways or repeating.
The code is using two ways:
Use of repeat/0
It is recursive, e.g.
process(y) :-
...
process(Respond).
This is making it hard to get the code working.
Problem 4
Duplicate code, e.g.
write("Name?:"),
read(Name),
assert(Name),nl,
write("Age?:"),
read(Age),
assert(Age),
write("Continue(y or n)?:"),
read(Respond),
process(Respond).
Duplicated code is more likely to lead to problems when one copy is corrected and the other copy is not corrected.
Problem 1 fix
Make the input a fact before storing in the database with assert/1, e.g.
Values in variables
Name
Age
Variables converted to facts by adding a functor
name(Name)
age(Age)
The facts used with assert/1
assert(name(Name))
assert(age(Age))
Problem 2 fix
Use read_string/5, e.g.
read_string(user, "\n", "\r", End, Name)
This reads the input into the variable Name as a string. Now that the input is a string, and not a Prolog term, the period is no longer required. There are predicates that operate on strings.
Problem 3 fix
Use the recursion form and remove repeat/0.
This could also use repeat/0 instead of recursion. The corrected code below uses recursion to demonstrate the change to process/1.
Problem 4 fix
Just refactor the code. You can see this in the corrected code at the end.
Now with the fixes in place.
Change 1
Since the input for continue is no longer a term, e.g. y or n, but a string, the parameter for process needs to be a string, e.g.
process("y") :-
process("n") :-
Change 2
Age will be asserted as a string but would be better asserted as an integer.
number_string/2 can solve this, e.g.
number_string(Age_n,Age),
assert(age(Age_n))
Change 3
user27815 Asked in a comment:
do you need the cut in process("n") :- !. ?
Since
process(Respond).
is not creating a choice point, the cut is not needed.
Corrected code:
update :-
% Respond will be read as a string and not as a term, so it needs "".
process("y").
process("y") :-
write('Name: '),
read_string(user, "\n", "\r", End, Name),
assert(name(Name)),
write("Age: "),
read_string(user, "\n", "\r", End, Age),
number_string(Age_n,Age),
assert(age(Age_n)),
write("Continue: (y or n) "),
read_string(user, "\n", "\r", End, Respond),
process(Respond).
process("n").
Example run:
?- update.
Name: Fred
Age: 30
Continue: (y or n) y
Name: Jim
Age: 21
Continue: (y or n) n
true.
To check that the database was updated use listing/1
?- listing(name/1).
:- dynamic name/1.
name("Fred").
name("Jim").
true.
?- listing(age/1).
:- dynamic age/1.
age(30).
age(21).
true.
A free enhancement.
Keeping the facts of name and age separate doesn't keep the relation between them intact. A better solution would be a person fact with both Name and Age values.
Here is the necessary modified code.
update :-
% Respond will be read as a string and not as a term, so it needs "".
process("y").
process("y") :-
write('Name: '),
read_string(user, "\n", "\r", End, Name),
write("Age: "),
read_string(user, "\n", "\r", End, Age),
number_string(Age_n,Age),
assert(person(Name,Age_n)),
write("Continue: (y or n) "),
read_string(user, "\n", "\r", End, Respond),
process(Respond).
process("n").
Example run:
?- update.
Name: Fred
Age: 30
Continue: (y or n) y
Name: Jim
Age: 21
Continue: (y or n) n
true.
To check that the database was updated use listing/1
?- listing(person/2).
:- dynamic person/2.
person("Fred", 30).
person("Jim", 21).
true.
After noticing your deleted answer.
In your deleted answer you have
?- person(name(N), age(A)).
N = nancy,
A = 22;
N= steve,
A = 100;
true.
The change needed for this variation of the fact to be created is
assert(person(name(Name),age(Age_n)))
however that might not be the optimal way to go.
In Prolog, positions typically indicate the meaning of a value, e.g. first position is name and second position is age. In this variation by adding the functors name and age to the fact person/2 you are duplicating known knowledge, but more importantly the possibility the amount of work Prolog has to do.
For example:
If the fact was person(Name,Age). to get at Name and Age Prolog only needs one unification. But with person(Name,Age). Prolog now needs to unify with person(name(nancy),age(22)) then to get Name has to unify again with name(nancy) and to get Age has to unify with age(22). You could also use person(name(Name),age(Age)). which requires only one unification, but now makes your code more verbose.
When first learning Prolog this crutch helps, but when working with larger data sets, this starts to impact performance.
Another item of note in your deleted answer is that the names of the people are still based on using read/1, e.g. nancy and steve. While a lot of Prolog examples do this, there is no requirement to keep them as such, they can be strings. Odds are the code will never need to exactly match on nancy or steve and instead will always reference them as a value in a variable. The nice thing about keeping them as strings is that when writing them out, they will appear correctly as Nancy and Steve.
This is because assert does not work on variables. It asserts a fact or rule; in other words, assert(something) asserts that something must be true.
From the SWI-Prolog documentation:
Assert a clause (fact or rule) into the database.
An integer value is not a rule or a fact. It is (in this case) an integer, not something that evaluates to a boolean value. There's no point in asserting a value.
I would write some helpers:
read_assert(P,V) :- format('~w ? ',[P]), read(V), A =.. [P,V], assert(A).
?- maplist(read_assert, [name,age], Vs).
name ? capellic.
age ? 99.
Vs = [capellic, 99].
?- name(N).
N = capellic.

Compile time testfor 'atoms'

Completely new to prolog. Interesting journey so far in trying to change how I think, so appreciate any help here.
I am trying to assert facts for a pre-defined set of names. For example, assume I have a a set of people [alice, bob, ...] in one file. I would like to assert facts about these folks in other files, but want to make sure that these folks exist and that is checked when the facts are loaded/compiled(?).
For example, assume I don't have 'chuck' in the list and I make an assertion:
user: swipl app.pl
?- full_name(chuck, "Charlie Steel").
should result in an error.
What is the best way I can do this?
So, here's the code I came up with:
person(deborah).
person(tony).
read_my_file(Filename) :-
open(Filename, read, In),
read_my_file1(In),
close(In).
read_my_file1(In) :-
read(In, Term),
( Term == end_of_file
-> true
; assert_or_abort(Term),
read_my_file1(In)
).
assert_or_abort(Term) :-
( full_name(Person, Name) = Term
-> ( person(Person)
-> assertz(full_name(Person, Name))
; format(user, '~w is not a person I recognize~n', [Person])
)
; format(user, '~w is not a term I know how to parse~n', [Term])
).
The trick here is using read/2 to obtain a Prolog term from the stream, and then doing some deterministic tests of it, hence the nested conditional structure inside assert_or_abort/1. Supposing you have an input file that looks like this:
full_name(deborah, 'Deborah Ismyname').
full_name(chuck, 'Charlie Steel').
full_name(this, has, too, many, arguments).
squant.
You get this output:
?- read_my_file('foo.txt').
chuck is not a person I recognize
full_name(this,has,too,many,arguments) is not a term I know how to parse
squant is not a term I know how to parse
true.
?- full_name(X,Y).
X = deborah,
Y = 'Deborah Ismyname'.

Obtain intermediate variables assignments

I am trying to use Prolog to represent state of a room.
I have a set of rules and a set of facts, but sometimes some of the facts are
not defined. For instance, temperature in a room can decrease due to either
cooling or opening a window, but sometimes I do not have a window sensor.
% Rules
temperature_trend(decrease) :-
cooling(on).
temperature_trend(decrease) :-
window(open).
% Facts
cooling(off).
%window(close). % Unknown, I do not have a window sensor
% Main
main() :-
temperature_trend(decrease).
If I run this program I would get an undefined procedure error. I can deal with
this by explicitly setting the window
status to "anything" with window(W). (I programmatically prepare the Prolog
source, so this is quite easy).
Now the query temperature_trend(decrease)
would succeed because window(W) would lead to window(open). However, in this
case I want to know that W = open.
Is there a way to return the variable assignments for this fact? Or maybe am I approaching the problem in the wrong way?
Note that the
rule tree could be arbitrarily deep, for instance I could add a new rule next_temperature(lower) :- temperature_trend(decrease). and I still
want to know that next_temperature(lower) succeeds only by setting W = open. Terms are also more complex
because they also have a time index (T = 232).
Maybe one option would be to return a list of assignments, which would be empty
if all facts were known.
Write a meta-interpreter that gives you what is true, e.g.,
prove(Goal, True) :-
phrase(prove(Goal), True).
prove(true) -->
!.
prove((A,B)) -->
!,
prove(A),
prove(B).
prove((A;B)) -->
!,
( prove(A)
; prove(B)
).
prove(Fact) -->
[Fact],
{ clause(Fact, Body) },
prove(Body).
Now, given window(_), we get:
?- prove(temperature_trend(decrease), L).
L = [temperature_trend(decrease), window(open)].
Lots of variations are possible!

use operator as constructor in prolog

I'm writing a function called leagalCourse, it takes just one parameter, a course list. A course like, for example, john+mary+94 would represent a project done by John and Mary with a mark of 94.
It should be true if the course data is “legal”, which means that it must not have a project with the same name twice such as john+john+70.
There also must not be two projects in the list containing the same pair of students. So if there’s a project harry+ron+82 in the list, it would be illegal for the list also to contain harry+ron+90 or ron+harry+63.
There is a sample output:
?- legalCourse([one+two+3,four+five+6,one+six+7]).
true.
?- legalCourse([one+two+3,four+four+6,one+six+7]).
false.
?- legalCourse([one+two+3,four+five+6,one+two+7]).
false.
?- legalCourse([one+two+3,two+one+6,one+six+7]).
false.
This is what I tried:
legalCourse([]).
legalCourse(X) :-
diffName(X).
legalCourse([Project|M]):-
diffName(Project),
not(samePair([Project|M])),
legalCourse(M).
diffName(Name1+Name2+_) :-
Name1 \= Name2.
/*can not have duplicated group*/
samePair([Name1+Name2+_|More]) :-
append([[head],tail,More]),
member(Name1,[head]),
member(Name2,[head]).
The function partially worked before I added the samePair predicate.
I think this works, you need to switch the vars and check both are different in check_no_dups/1.
legalCourse(List):-
maplist(triple_double,List,ListDouble),
check_no_dups(ListDouble).
check_no_dups([]).
check_no_dups([H|T]):-
H =X+Y,
maplist(dif(H),T),
H2 =Y+X,
maplist(dif(H2),T),
check_no_dups(T).
triple_double(X+Y+_Z,X+Y):-dif(X,Y).

Prolog dict predicate matching

Given this program, why am I forced to define every atom in the predicate, even if they're anonymous. Why is it that undefined variables in a dict predicate aren't thought of as anonymous?
funt2(X) :-
X = point{x:5, y:6}.
evalfunt(point{x:5, y : 6}) :-
write('hello world!').
evalfunt(point{x:_, y : _} ) :-
write('GoodBye world!').
Why can't I just say
evalfunt(point{x:5}) :-
write('GoodBye world!').
^that won't match, by the way.
I may as well just use a structure if I have to define every possible value in the dict to use dicts.
What's the motivation here? Can I do something to make my predicate terse? I'm trying to define a dict with 30 variables and this is a huge roadblock. It's going to increase my program size by a magnitude if I'm forced to define each variables (anonymous or not).
Dict is just a complex data type, like tuple, which has data AND structure. If you have, for example two facts:
fact(point{x:5, y:6}).
fact(point{x:5}).
Then the query
fact(point{x:_}).
will match the second one, but not the first one.
And the query
fact(point{x:_, y:_}).
Will match the first one, but not the second.
Now, if you want to match facts of the form fact(point{x:_, y:_, z:_}) only by one specific field, you can always write a helper rule:
matchByX(X, P) :- fact(P), P=point{x:X, y:_, z:_}.
So having facts:
fact(point{x:5, y:6, z:1}).
fact(point{x:1, y:2, z:3}).
fact(point{x:2, y:65, z:4}).
and quering
matchByX(1, P).
will return:
P = point{x:1, y:2, z:3}
UPDATE:
Moreover, in SWI-Prolog 7 version the field names can be matched as well, so it can be written in much more generic way, even for facts with different structures:
fact(point{x:5, y:6, z:1}).
fact(point{x:1, y:2}).
fact(point{x:2}).
fact(point{x:2, y:2}).
matchByField(F, X, P) :- fact(P), P.F = X.
So query:
?- matchByField(x, 2, P).
P = point{x:2} ;
P = point{x:2, y:2}.
I was able to accomplish what I needed by doing the following
checkiffive(Y) :-
get_dict(x, Y, V), V=5.
You need to use the built in methods for unifying values from a dict.
Described in chapter 5.4 of the SWI prolog reference
http://www.swi-prolog.org/download/devel/doc/SWI-Prolog-7.1.16.pdf

Resources