Re: where do I think wrong
"Richard A. O'Keefe" <[email protected]>
| Newsgroups | gmane.comp.ai.prolog.swi |
|---|---|
| Message-ID | <[email protected]> |
On 13/03/2014, at 9:04 PM, Roelof Wobben wrote:
>
> parent(mans,gerda).
> parent(mans,roelof).
> parent(marie,gerda).
> parent(marie,roelof).
> parent(dagmar,denise).
> parent(dagmar,kimberly).
> parent(ronald,denise).
> parent(ronald,kimberly).
> parent(chantal,tamara).
> parent(roelof,tamara).
> parent(jan,chantal).
> parent(jan,dagmar).
(1) Where is the comment saying which argument is what?
Is mans a parent of gerda, or
is gerda a parent of mans?
I entreat you in the name of suffering humanity,
ADD A COMMENT
% parent(P, C) is true when P and C are atoms
% naming people and P is one of C's parents.
I am serious about the "suffering humanity" part.
Failing to provide this information waste people's
time as they scour the rest of your program for
clues about something they needed to know and you
knew and chose to conceal. I don't care *what*
the programming language is, that's bad style.
(2) There is a key on your keyboard you may have
forgotten about, though it's easily the biggest
and easiest to use. USE IT. It's called the
space bar, and it's the big one at the bottom.
Like most programming languages, Prolog uses the
comma for several things:
- to separate goals
=> put a newline after
- to separate the arguments of goals
=> put a space after
- to separate the fields of data structures
=> I usually prefer NOT to put a space, but
there are some who do, and I'll not say
they're wrong.
When a construction has parts that need to be
*perceived* as parts, ensure that there is
enough white space.
Again, this is largely language-independent.
If there is any programming language in which
it is good style to run the arguments of a
procedure together in one space-less blob, I
hope I never see it.
> is_father(F,C) :-
> parent(F,C), male(F).
Better:
father_child(Father, Child) :-
parent_child(Father, Child),
male(Father).
(3) You can get away with one-letter variable names in
toy programs, but it is a cruel habit to get into.
I once had to maintain a couple of thousand lines
of Prolog where every variable was one letter or
one letter and one digit, and I couldn't take a
step until I had changed all the names to something
meaningful.
Again, this is largely language-independent.
BASIC was the last programming language I know of
to insist on 1-letter or 1-letter-1-digit names,
and modern BASIC doesn't do that any more.
What if someone wrote
is_father(M,T) :- parent(M,T), male(M).
(Looks obvious to _me_: M = Matua, T = Tamaiti.)
> have_children(M,F) :-
> is_mother(M,Z),
> is_father(F,Z).
(3) To start with, this is a perfect example of how
one-letter names can confuse. I initially read
"M" and "F" as "Male" and "Female" respectively...
(4) If you say in English that M and F "have children"
you are definitely implying that they have more than
one. This predicate only tests whether they have at
least one.
(5) What's more, suppose
richard father-of abigail
richard father-of susanna
jeanene mother-of abigail
jeanene mother-of susanna
Then
have_children(richard, jeanene)
can be proven true in two different ways.
You get this interaction:
?- have_children(Woman, richard).
Woman = jeanene ;
Woman = jeanene.
It is important to understand that Prolog doesn't find
*solutions* to your query, it finds *proofs*, and one
solution may have many proofs.
We can fix that by putting hacky code here,
but the best way is to refactor the data design.
It's certainly what I would do if this were a
real data base.
I would introduce 'consortships'. (OED sense 2b.)
So there would be facts
consortship_man( 273, richard).
consortship_woman(273, jeanene).
parentage(abigail, 273).
parentage(susanna, 273).
or something like that, and rules
man_and_woman_who_had_some_child(Man, Woman) :-
consortship_man(Consortship, Man),
consortship_woman(Consortship, Woman),
once(parentage(_Child, Consortship)).
child_father(Child, Father) :-
parentage(Child, Consortship),
consortship_man(Consortship, Father).
or something like that.
> list_parents(C) :-
> parent(X,C),
> tab(5),
> write(X),
> nl,
> fail.
> list_parents(_).
(6) I really dislike having the parts of a failure-driven loop
scattered across several clauses. I find it clearer if
you have
list_parents(Child) :-
( parent(Parent, Child),
tab(5), write(Parent), nl,
fail
; true
).
> display_parents(C) :-
> write('De ouders van '),
> write(C),
> write(' zijn :'),
> nl,
> nl,
> list_parents(C).
> display_parents(_).
(7) Here the second clause is worse than useless.
>
> have_siblings(C) :-
> is_father(X,C),
> is_father(X,S),
> is_mother(Y,S),
> is_mother(Y,C),
> C \= S,
> write(S).
(8) You really want to factor out the common pattern here.
child_father_mother(Child, Father, Mother) :-
is_father(Father, Child),
is_mother(Mother, Child).
have_siblings(Child) :-
child_father_mother(Child, Father, Mother),
child_father_mother(Sibling, Father, Mother),
Sibling \== Child,
write(Sibling).
(9) You have a predicate whose purpose is to LIST
the siblings of someone, but you have named it
as if its purpose were to TEST whether someone
had any siblings. Write
are_full_siblings(Child, Sibling) :-
child_father_mother(Child, Father, Mother),
child_father_mother(Sibling, Father, Mother),
Sibling \== Child.
list_full_siblings_of(Child) :-
foreach(are_full_siblings(Child, Sibling),
writeln(Sibling)).
> display_siblings(C) :-
> write('De broers/zusters van '),
> write(C),
> write(' zijn :'),
> nl,
> nl,
> have_siblings(C).
> display_siblings(_).
(7) again. Or it would be if have_siblings/1 were the
other half of the failure-driven loop you think it is.
With the list_siblings/1 predicate above, delete that
second clause.
>
>
> It worked fine if a mother and father are known.
> So I want to change it to this :
>
> have_siblings(C) :-
> is_father(X,C),
> is_father(X,S);
> is_mother(Y,S),
> is_mother(Y,C),
> C \= S,
> write(S).
(10)You have a scope issue with ';'.
ALWAYS write
( <alternative 1>
; <alternative 2>
...
; <alternative n>
)
with the semicolons at the front, so they are
visibly NOT commas, and while there are times
that you don't really need the parentheses,
ALWAYS put them in.
Do NOT put extra parentheses around each alternative,
they just make it harder to read.
Again, you want to factor out "are these people half
siblings" from "show the half siblings of this person".
are_half_or_fullsiblings(Child, Sibling) :-
( is_father(Father, Child),
is_father(Father, Sibling)
; is_mother(Mother, Child),
is_mother(Mother, Sibling)
),
Sibling \== Child.
list_half_siblings(Child) :-
foreach(are_half_or_full_siblings(Child, Sibling),
writeln(Sibling)).
However, note that abigail and susanna are full siblings,
so they have the same father AND the same mother, so there
are TWO ways for are_half_siblings(abigail, susanna) to be
proven, and list_half_siblings(abigail) will write
susanna twice.
One way to deal with that is to say that two people
are half_or_full siblings if they have the same father
or they have the same mother but NOT the same father.
% In the following predicates, X and Y stand for
% people of indeterminate sex at the same genealogical level.
same_father(X, Y) :-
is_father(Father, X),
is_father(Father, Y).
same_mother(X, Y) :-
is_mother(Mother, X),
is_mother(Mother, Y).
half_siblings(X, Y) :-
( same_father(X, Y),
\+ same_mother(X, Y)
; same_mother(X, Y),
\+ same_father(X, Y)
).
half_or_full_siblings(X, Y) :-
( same_father(X, Y),
X \== Y
; same_mother(X, Y),
\+ same_father(X, Y)
).
full_siblings(X, Y) :-
same_father(X, Y),
X \== Y,
same_mother(X, Y).
You can get away with using single-letter variables
if you set up an EXPLICIT convention for what they mean.