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
.