Automating my pet debugging strategy in SWI-Prolog - prolog

I have a very straightforward question I'd be happy to receive any guidance on.
I'm working on a Definite Clause Grammar, and I'm running spot checks on its output. If a parse tree is confusing to me, I want to trace it back to the predicate that generated that part of the tree. So what I do is insert numeric atoms into my predicates. Like so:
sentence(sentence(Subject, Verb, Object)) --> Subject, Verb, Object.
becomes
sentence(sentence(736, Subject, Verb, Object)) --> Subject, Verb, Object.
I can then search for the number 736 and examine that particular predicate to see why it was chosen by Prolog. This has become very handy as my grammar has ballooned in size. But it's inconvenient to have to make these text edits whenever I want to debug.
Is there some elegant Prolog rule I could add to the grammar when I want to debug in this way, something that would attach a unique i.d. to each predicate?

This is highly implementation-specific, but SWI-Prolog has a source_location/2 predicate that, called inside a term_expansion/2 rule, gives you the file name and line number of the clause being expanded.
So you can use something like the following:
term_expansion(Head --> Body, EnhancedHead --> Body) :-
source_location(File, Line),
format('~w --> ~w at ~w:~w~n', [Head, Body, File, Line]),
Head =.. [Functor, Arg1 | Args],
Arg1 =.. [ArgFunctor | ArgArgs],
EnhancedArg1 =.. [ArgFunctor, File:Line | ArgArgs],
EnhancedHead =.. [Functor, EnhancedArg1 | Args].
hello -->
[world].
sentence(sentence(Subject, Verb, Object)) -->
[Subject, Verb, Object].
Note that this term_expansion/2 will print the log message for every -->/2 rule in the program:
hello --> [world] at /home/isabelle/hello.pl:9
sentence(sentence(_2976,_2978,_2980)) --> [_2976,_2978,_2980] at /home/isabelle/hello.pl:12
But it will then fail if the rule's head doesn't have at least one argument, and the first argument doesn't have at least one argument of its own. This is fine, failure just means "don't rewrite this term":
?- listing(hello).
hello([world|A], A).
true.
?- phrase(hello, Hello).
Hello = [world].
But sentence//1 will be rewritten:
?- listing(sentence).
sentence(sentence('/home/isabelle/hello.pl':12, A, B, C), [A, B, C|D], D).
true.
?- phrase(sentence(sentence(Position, S, V, O)), [isabelle, likes, prolog]).
Position = '/home/isabelle/hello.pl':12,
S = isabelle,
V = likes,
O = prolog.
You could build on this, maybe with a separate operator ---> to mark only those rules you really want rewritten. I think having this extra implicit argument is a recipe for lots of unexpected failures when you try to unify something with the actual underlying term, not the term as it appears in the source code.
So maybe a better approach would be something like this:
sentence(sentence(#position, Subject, Verb, Object)) -->
[Subject, Verb, Object].
and a corresponding term_expansion/2 rule that looks for these #position terms and replaces them accordingly.

Related

Getting hold of a variable in complex compound term in Prolog

I have a Prolog sentence parser that returns a sentence (passed into it as a list) split into two parts - a Noun_Phrase and a Verb_Phrase. See example below:
sentence(Sentence, sentence(np(Noun_Phrase), vp(Verb_Phrase))) :-
np(Sentence, Noun_Phrase, Remainder),
vp(Remainder, Verb_Phrase).
Now I want to take the Noun_Phrase and Verb_Phrase and pass them into another Prolog predicate, but first I want to extract the first term from the Verb_Phrase (which should always be a verb) into a variable and the rest of the Verb_Phrase into another one and pass them separately into the next predicate.
I thought about using unification for this and I have tried:
sentence(Sentence, sentence(np(Noun_Phrase), vp(Verb_Phrase))),
[Head|Tail] = Verb_Phrase,
next_predicate(_, Noun_Phrase, Head, Tail, _).
But I am getting ERROR: Out of local stack exception every time. I think this has something to do with the Verb_Phrase not really being a list. This is a possible isntance of Verb_Phrase:
VP = vp(vp(verb(making), adj(quick), np2(noun(improvements))))
How could I get the verb(X) as variable Verb and the rest of the term as varaible Rest out of such compound term in Prolog?
You could use =../2 like:
Verb_Phrase=..[Verb|Rest_Term_list].
Example:
?- noun(improvements)=..[Verb|Rest_Term_list].
Verb = noun,
Rest_Term_list = [improvements].

Prolog build rules from atoms

I'm currently trying to to interpret user-entered strings via Prolog. I'm using code I've found on the internet, which converts a string into a list of atoms.
"Men are stupid." => [men,are,stupid,'.'] % Example
From this I would like to create a rule, which then can be used in the Prolog command-line.
% everyone is a keyword for a rule. If the list doesn't contain 'everyone'
% it's a fact.
% [men,are,stupid]
% should become ...
stupid(men).
% [everyone,who,is,stupid,is,tall]
% should become ...
tall(X) :- stupid(X).
% [everyone,who,is,not,tall,is,green]
% should become ...
green(X) :- not(tall(X)).
% Therefore, this query should return true/yes:
?- green(women).
true.
I don't need anything super fancy for this as my input will always follow a couple of rules and therefore just needs to be analyzed according to these rules.
I've been thinking about this for probably an hour now, but didn't come to anything even considerable, so I can't provide you with what I've tried so far. Can anyone push me into the right direction?
Consider using a DCG. For example:
list_clause(List, Clause) :-
phrase(clause_(Clause), List).
clause_(Fact) --> [X,are,Y], { Fact =.. [Y,X] }.
clause_(Head :- Body) --> [everyone,who,is,B,is,A],
{ Head =.. [A,X], Body =.. [B,X] }.
Examples:
?- list_clause([men,are,stupid], Clause).
Clause = stupid(men).
?- list_clause([everyone,who,is,stupid,is,tall], Clause).
Clause = tall(_G2763):-stupid(_G2763).
I leave the remaining example as an easy exercise.
You can use assertz/1 to assert such clauses dynamically:
?- List = <your list>, list_clause(List, Clause), assertz(Clause).
First of all, you could already during the tokenization step make terms instead of lists, and even directly assert rules into the database. Let's take the "men are stupid" example.
You want to write down something like:
?- assert_rule_from_sentence("Men are stupid.").
and end up with a rule of the form stupid(men).
assert_rule_from_sentence(Sentence) :-
phrase(sentence_to_database, Sentence).
sentence_to_database -->
subject(Subject), " ",
"are", " ",
object(Object), " ",
{ Rule =.. [Object, Subject],
assertz(Rule)
}.
(let's assume you know how to write the DCGs for subject and object)
This is it! Of course, your sentence_to_database//0 will need to have more clauses, or use helper clauses and predicates, but this is at least a start.
As #mat says, it is cleaner to first tokenize and then deal with the tokenized sentence. But then, it would go something like this:
tokenize_sentence(be(Subject, Object)) -->
subject(Subject), space,
be, !,
object(Object), end.
(now you also need to probably define what a space and an end of sentence is...)
be -->
"is".
be -->
"are".
assert_tokenized(be(Subject, Object)) :-
Fact =.. [Object, Subject],
assertz(Fact).
The main reason for doing it this way is that you know during the tokenization what sort of sentence you have: subject - verb - object, or subject - modifier - object - modifier etc, and you can use this information to write your assert_tokenized/1 in a more explicit way.
Definite Clause Grammars are Prolog's go-to tool for translating from strings (such as your English sentences) to Prolog terms (such as the Prolog clauses you want to generate), or the other way around. Here are two introductions I'd recommend:
http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse29
http://www.pathwayslms.com/swipltuts/dcg/

Construct clause step by step

I would like to construct a clause after a series of steps. For instance, if i verify a condition then i assert a part of a clause. If "the pen is red" i obtain:
color(pen, red)
if "the pen is on the table":
on(pen, table)
if "the table is blue":
color(table, blue)
At the end I have to get:
color(pen, red), on(pen, table), color(table,blue).
I would like to insert the final clause in an external file. How can I do?
EDIT:
I insert a text similar to the above and deduct these separate predicate:
first color(pen,red), second on(pen,table), third color(table,blue)
I would like to obtain a clause that is :
text(t1):- color(pen, red), on(pen, table), color(table,blue).
and this clause must be inserted in a file.
INPUT: single predicate.
OUTPUT: one clause with all predicates.
I'd attack the problem with DCGs.
sentence(S) --> color_statement(S) ; on_statement(S).
det --> [a].
det --> [the].
color_statement(color(Noun, Color)) --> det, [Noun], [is], color(Color).
color(Color) --> [Color], { color(Color) }.
color(red). color(blue).
on_statement(on(Noun, Place)) --> det, [Noun], [is,on], det, [Place].
This is assuming you have some kind of tokenization in place, but for demo purposes, you'll find this "works":
?- phrase(sentence(S), [the,pen,is,on,the,bookshelf]).
S = on(pen, bookshelf).
You will no doubt need to extend these rules for your purposes. You can find tokenization stuff by searching, and only you know exactly the kinds of nouns and modifiers you want to support, so this is really just a sketch of how this could proceed.
From here you're going to create another rule to handle multiple statements.
clause([]) --> [].
clause([S|Rest]) --> sentence(S), ['.'], clause(Rest).
Testing it works like so:
?- phrase(clause(S), [the,pen,is,on,the,bookshelf,'.',the,pen,is,red,'.']).
S = [on(pen, bookshelf), color(pen, red)]
So these are the clauses you want. Now you just need a predicate to bring them together.
list_and([X,Y], (X,Y)).
list_and([X|Xs], (X,Rest)) :- list_and(Xs, Rest).
clause_for(Name, Tokens, Predicate) :-
phrase(clause(Parts), Tokens),
list_and(Parts, AndSequence),
Predicate = (Name :- AndSequence).
This does basically what you want, but you need to furnish a name for your predicate:
?- clause_for(bob, [the,pen,is,on,the,bookshelf,'.',the,pen,is,red,'.'], P).
P = (bob:-on(pen, bookshelf), color(pen, red))
Hope this helps!

Determine the type of characters

I would like to determine in Prolog the type of a string of characters, if it is alphabetic, alphanumeric or numeric.
For example:
"I use this page" alphabetic
"0c0d24e" alphanumeric
How can i do?
the predicate available is char_type/2, or better, code_type/2.
To apply to each code in string, use maplist/2. The only problem it's the wrong arguments order of code_type. Then a service predicate is needed (or download lambda, if you're using SWI-Prolog, with ?- pack_install(lambda).).
Without lambda:
code_type_(X,Y) :- code_type(Y,X).
?- maplist(code_type_(alpha), "abc").
true.
With lambda:
?- [library(lambda)].
?- maplist(\C^code_type(C,alpha), "abc").
true.
edit after comments, it's apparent that more flexible parsing is required. A DCG it's the recommended way to go: library(dcg/basics) offers some prebuilt 'categorizer', and highlights the proper way to write your own, combining with code_type: for instance, here is a recently added rule:
%% prolog_var_name(-Name:atom)// is semidet.
%
% Matches a Prolog variable name. Primarily intended to deal with
% quasi quotations that embed Prolog variables.
prolog_var_name(Name) -->
[C0], { code_type(C0, prolog_var_start) }, !,
prolog_id_cont(CL),
{ atom_codes(Name, [C0|CL]) }.
prolog_id_cont([H|T]) -->
[H], { code_type(H, prolog_identifier_continue) }, !,
prolog_id_cont(T).
prolog_id_cont([]) --> "".
see how code_type/2 is used to qualify single characters...
more edit - note: untested
qualify_atom(Atom, Type) :-
atom_codes(Atom, Codes),
qualify_codes(Codes, Type).
qualify_codes(Codes, Type) :-
( maplist(code_type_(alnum), Codes)
-> Type = alnum
; maplist(code_type_(alpha), Codes)
-> Type = alpha
; Type = unknown
).
then, to work on a list
?- maplist(qualify_atom, Atoms, Types).
edit
An update of this answer is mandatory: since library(yall) has been released in SWI-Prolog, and is autoloaded, we can now write:
?- maplist([C]>>code_type(C,alpha), `abc`).
Also, note the change in literal representation: double quotes in SWI-Prolog ver.7+ don't represent anymore a list of character codes.

Prolog simple program

I am writing a little Prolog program, which is expected to do something as following:
?-input([allan,is,a,name]).
true.
?-input([Is,allan,a,name]).
true.
and here is my code:
% Simple answering agent
input(Text) :-
phrase(sentence(S), Text),
perform(S).
sentence(statement(S)) --> statement(S).
sentence(query(Q)) --> query(Q).
statement(Statement) -->
[Thing, 'is', 'a', Category],
{ Statement =.. [Category, Thing]}.
query(Query) -->
['Is', Thing, 'a', Category],
{ Query =.. [Category, Thing]}.
perform(statement(S)) :- asserta(S).
perform(query(Q)) :- Q.
the input([allan,is,a,name]). part seems working fine, but there is a trouble with the query part, for which if I type in input([Is,allan,a,name])., it prints
Is = 'Is'
Can someone please take a look at this problem for me, thank you.
Well, the problem is that Is is a variable and thus prolog instantiates it (with 'Is'). It would be good practise to ensure that all the members of the list are atoms but for a quick fix you could just do:
query(Query) -->
[_, Thing, 'a', Category],
{ Query =.. [Category, Thing]}.
this way, Is won't get instantiated and prolog will just say true. The only problem is that a statement could be interpreted as a query:
9 ?- input([allan, is, a, name]).
true ;
false.
10 ?- input([is, is, a, name]).
true .
11 ?- input([allan, is, a, name]).
true ;
true.
which can be fixed with some cuts (or be saying that Thing should be different than 'is' - if that's acceptable)
Edit: for a more general solution: it really depends on what kind of sentences you want to parse and what compromises the user can make. For example, it might be possible to ask him to give you words that are prolog atoms; if a word that starts with an upper case letter is requested he will have to use ''. Otherwise, it will be better to just give them in a string/atom ('Is allan a name' or "Is allan a name"). It's easy to separate it to individual atoms: use atomic_list_concat/3. For what is allan you still don't need to do something special; it's a 3 word sentence while the rest were 4 so you can separate it immediately.

Resources