Datalog computational class? - computation-theory

Datalog is not Turing complete.
But what is its computational class?
Is it equivalent to Finite state machine or Pushdown machine (i.e. context free) ... or is it something in between?

Let's assume we have access, whether built-in or defined in the language by us, to the following predicates:
Head(x, y) iff y is a list and x is the first element in the list
Tail(x, y) iff x and y are lists and x is the same as y but is missing y's first element
Equal(x, y) iff x and y are the the same thing
First off, I think it's clear that this language can accept all the regular languages. By the Myhill-Nerode theorem, all states in a minimal DFA for a regular language correspond to a unique equivalence class under the indistinguishability relation. It seems like we could have one predicate per equivalence class/state to represent whether a list corresponding to an input string belongs to that class, and then another predicate that is true only if one of the predicates corresponding to an accepting state is true. So, for the language over {a, b} with an even number of a and an odd number of b, a minimal DFA has four states:
O
|
V
q0<---a--->q1
^ ^
| |
b b
| |
V V
q2<---a--->q3
Here, q2 is the only accepting state. Our DataLog program might look like:
Q0(()).
Q0(x) :- Head(y, x), Equal(y, 'a'), Tail(z, x), Q1(z).
Q0(x) :- Head(y, x), Equal(y, 'b'), Tail(z, x), Q2(z).
Q1(x) :- Head(y, x), Equal(y, 'a'), Tail(z, x), Q0(z).
Q1(x) :- Head(y, x), Equal(y, 'b'), Tail(z, x), Q3(z).
Q2(x) :- Head(y, x), Equal(y, 'a'), Tail(z, x), Q3(z).
Q2(x) :- Head(y, x), Equal(y, 'b'), Tail(z, x), Q0(z).
Q3(x) :- Head(y, x), Equal(y, 'a'), Tail(z, x), Q2(z).
Q3(x) :- Head(y, x), Equal(y, 'b'), Tail(z, x), Q1(z).
EvenAOddB(x) :- Q2(x).
Based on this example I think it's clear we can always encode transitions in this fashion and so any regular language can be accepted. Thus, DataLog is at least as powerful as deterministic finite automata.
We can define this:
// Last(x, y) iff x is the last element of y
Last(x, y) :- Head(x, y), Tail(z, y), Equal(z, ()).
// AllButLast(x, y) iff x and y are the same list but x is missing the last element of y
AllButLast((), (x)).
AllButLast(x, y) :- Head(w, x), Head(z, y), Equal(w, z),
Tail(w', x), Tail(z', y), AllButLast(w', z').
Now we can recognize lists corresponding to strings in the context-free language a^n b^n:
// ANBN(x) iff x is a list beginning with n 'a's followed by n 'b's
ANBN(()).
ANBN(x) :- Head(y, x), Equal(y, 'a'), Tail(z, x),
Last(w, z), Equal(w, 'b'), AllButLast(z', z),
ANBN(z').
It's easy to tweak that predicate to find the language of even-length palindromes, and from there it's easy to tweak to find the language of all palindromes. I feel confident we can also get it to accept languages like balanced parentheses, etc. Based on this experience, I am guessing that we can accept all the context-free languages.
Can we get a context-sensitive language? Let's try a^n b^n c^n. If we assume DataLog has built-in predicates like this for integer types:
Zero(x) iff x is equal to zero
Successor(x, y) iff x and y are integers and x = y + 1
Then I think we can, as follows:
ANBNCN(()).
ANBNCN(x) :- Zero(y), ANBNCNZ(x, y).
ANBNCNZ(x, y) :- BN(x, y).
ANBNCNZ(x, y) :- Head(w, x), Equal(w, 'a'),
Last(z, x), Equal(z, 'c'),
Tail(u, x), AllButLast(v, u),
Successor(r, y), ANBNCNZ(v, r).
BN(x, y) :- Head(w, x), Equal(w, 'b'),
Successor(y, z), Tail(u, x),
BN(u, z).
What the above says is the following:
the empty string is in a^n b^n c^n
otherwise, a string is in a^n b^n c^n if f(s, 0) is true
f(s, n) is true if s consists only of n instances of 'b'
f(s, n) is true if s starts with a, ends with c, and f(s', n + 1) is true of everything in the middle
This should work since each recursive call of f(s, n) strips one a and one c off the ends and remembers how many it has counted. It then counts off that many instances of b once all the a and c are gone.
My feeling based on this is that we can probably do some or all of the context-sensitive languages as well. Probably, the lack of unbounded execution is precisely what distinguishes the linear-bounded automata (productions in phrase-structure grammars must have a RHS no longer than the LHS) from general unrestricted grammars (whose intermediate forms can grow and shrink arbitrarily).

Related

Calculating syntactic complexity of a prolog predicate

Currently working on an exercise where, given some predicate, the syntactic complexity has to be calculated. The syntactic complexity of some predicate is calculated as follows:
If the predicate is atomic or a function, its complexity is 2.
If the predicate is a variable, its complexity is 1.
For example, the syntactic complexity of loyalty(father(bob, Y), X) is worked out as follows:
loyalty = 2 (function)
father = 2 (function)
bob = 2 (atom)
Y = 1 (variable)
X = 1 (variable)
Total = 8
The approach taken was calculating such complexity if the predicate was in the form of a nested list, i.e. loyalty(father(bob, Y), X) = [loyalty, father, bob, Y, X], as follows:
complexity([], 0).
complexity([H|L], C) :- atomic(H), complexity(L, C1), C is C1+2.
complexity([H|L], C) :- var(H), complexity(L, C1), C is C1+1.
The remaining issue is converting the predicate to a flat list, as shown above. The ..= is useful, but its output is not complete, that is:
loyalty(father(bob, Y), X) ..= ["loyalty", "father(bob, Y)", "X"]
Any help would be appreciated.
You must apply =.. recursively as follows:
% term_to_list(+Term, -List)
term_to_list(Term, [Term]) :- var(Term), !.
term_to_list(Term, [Term]) :- atomic(Term), !.
term_to_list(Term, List) :-
compound(Term),
Term =.. Components,
maplist(term_to_list, Components, ListOfLists),
flatten(ListOfLists, List).
Example:
?- term_to_list(loyalty(father(bob, Y), X), L).
L = [loyalty, father, bob, Y, X].
Alternatively, you can define complexity/2 as follows:
% complexity(+Term, -Complexity)
complexity(Term, 1) :- var(Term), !.
complexity(Term, 2) :- atomic(Term), !.
complexity(Term, Complexity) :-
compound(Term),
Term =.. Components,
maplist(complexity, Components, Complexities),
sum_list(Complexities, Complexity).
Example:
?- complexity(loyalty(father(bob, Y), X), L).
L = 8.
Remark SWI-Prolog defines maplist/3 and sum_list/2 as follows:
maplist(Goal, List1, List2) :-
maplist_(List1, List2, Goal).
maplist_([], [], _).
maplist_([Elem1|Tail1], [Elem2|Tail2], Goal) :-
call(Goal, Elem1, Elem2),
maplist_(Tail1, Tail2, Goal).
sum_list(Xs, Sum) :-
sum_list(Xs, 0, Sum).
sum_list([], Sum, Sum).
sum_list([X|Xs], Sum0, Sum) :-
Sum1 is Sum0 + X,
sum_list(Xs, Sum1, Sum).

Prolog encoded integers, less/2(X, Y) predicate

A question states the following:
In Prolog, non-negative integers can be encoded as numerals given by 0 and its successors (with, for example, the numeral s(s(s(0))) encoding 3).
numeral(0).
numeral(s(X)) :- numeral(X).
Define the predicate less/2(X, Y) that holds when X and Y are numerals encoding non-negative integers x and y such that x < y. For example,
?- less(0, s(0)).
yes.
?- less(s(s(0)), s(s(0))).
no.
I have been able to come up with a solution for this question, however, it suffers from a limitation. Here is my solution:
less(X, s(X)) :- numeral(X).
less(X, Z) :- less(X, Y), less(Y, Z).
This solution correctly outputs a yes for inputs that satisfy this predicate. However, for inputs that expect a no, this solution seems to enter an endless recursion of some sort, and the program just keeps running, rather than outputting a no.
Please help.
I would do it like this:
less(0, s(Y)) :- numeral(Y).
less(s(X), s(Y)) :- less(X, Y).
?- less(0, s(0)).
true.
?- less(s(s(0)), s(s(0))).
false.
The idea is that 0 is less than any s(Y), where Y is a numeral. If X is not 0, then X is s(X'), and X = s(X') is less than Y = s(Y') iff X' is less than Y'.
This does only work if both X and Y are numerals. If X is not a numeral then it will get stuck somewhere in the recursion and "returns" false. Same for Y, except that there need to be a test at the end if the rest of Y is a numeral.
Try this:
less2(X, s(X)) :- numeral(X).
less2(X, s(Y)) :- less2(X,Y).
Seems to work for me; your solution could recurse endlessly though, because if there exists no value of Y between X and Z it will simply try everything under the sun.

The unification algorithm in Prolog

I'm trying to program the unification algorithm in Prolog to verify if two expressions can unify by returning boolean True/False:
EDIT.
I found this implementation usefull:
from: http://kti.mff.cuni.cz/~bartak/prolog/data_struct.html
unify(A,B):-
atomic(A),atomic(B),A=B.
unify(A,B):-
var(A),A=B. % without occurs check
unify(A,B):-
nonvar(A),var(B),A=B. % without occurs check
unify(A,B):-
compound(A),compound(B),
A=..[F|ArgsA],B=..[F|ArgsB],
unify_args(ArgsA,ArgsB).
unify_args([A|TA],[B|TB]):-
unify(A,B),
unify_args(TA,TB).
unify_args([],[]).```
Here is a partial implementation of something like the Martelli and Montanari unification algorithm described at https://en.wikipedia.org/wiki/Unification_(computer_science)#A_unification_algorithm. The comments for each part refer to the corresponding rewrite rule from the algorithm. Note that there is no need for an explicit conflict rule, we can just fail if no other rule applies.
% assuming a universe with function symbols g/2, p/2, q/2
% identical terms unify (delete rule)
unify(X, Y) :-
X == Y,
!.
% a variable unifies with anything (eliminate rule)
unify(X, Y) :-
var(X),
!,
X = Y.
% an equation Term = Variable can be solved as Variable = Term (swap rule)
unify(X, Y) :-
var(Y),
!,
unify(Y, X).
% given equal function symbols, unify the arguments (decompose rule)
unify(g(A, B), g(X, Y)) :-
unify(A, X),
unify(B, Y).
unify(p(A, B), p(X, Y)) :-
unify(A, X),
unify(B, Y).
unify(q(A, B), q(X, Y)) :-
unify(A, X),
unify(B, Y).
Examples:
?- unify(q(Y,g(a,b)), p(g(X,X),Y)).
false.
?- unify(q(Y,g(a,b)), q(g(X,X),Y)).
false.
?- unify(q(Y,g(a,a)), q(g(X,X),Y)).
Y = g(a, a),
X = a.
One or two things remain for you to do:
Generalize the decompose rule to deal with arbitrary terms. You might find the =.. operator useful. For example:
?- Term = r(a, b, c), Term =.. FunctorAndArgs, [Functor | Args] = FunctorAndArgs.
Term = r(a, b, c),
FunctorAndArgs = [r, a, b, c],
Functor = r,
Args = [a, b, c].
You will need to check if two terms have the same functor and the same number of arguments, and whether all corresponding pairs of arguments unify.
Find out if your professor would like you to implement the occurs check, and if yes, implement it.

Basic prolog issue with syntax of statement

I'm sort of a beginner to Prolog and I have a quick question. I've written a sister_of/2 predicate that goes like this:
sister_of(X, Y) :-
female(X),
parents(X, Z, W) == parents(Y,Z,W),
X \= Y.
I read this as X is a sister of Y if X is a female and the parents of X which are Z W are the same parents of Y, Z W and X and Y are not the same person. This isn't running for some reason though, so it must be a syntax issue, some insight would be fantastic. Thank you.
Prolog is not a functional language, it's a relational language. You define predicates (i.e. relations), not functions. Thus, your parents(X, Z, W) == parents(Y,Z,W) is just comparing two terms, parents(X, Z, W) and parents(Y,Z,W) for equality. Assuming that a parents/3 predicate is also defined, you want something like:
sister_of(X, Y) :-
female(X),
parents(X, Z, W),
parents(Y, Z, W),
X \= Y.
Well, you began the construction of reasoning alright, but failed during the comparison. Here's what you've missed:
sister_of(X, Y) :-
female(X),
parents(X, Z, W),
parents(Y, Z, W),
X \= Y.
Now the reasoning goes as follows:
X is a sister of Y;
if X is a female;
if X has parents Z and W;
if Y has parents Z and W;
and, of course, X is not Y, so that X is not a sister of itself.
Notice that, the comparison you performed earlier is not necessary (and in fact, does not mean what you expected), since Z and W become limited to be the parents of X in step 3. This means that, Z and W already have a determined value after step 3 -- they are bound, in other words.
After that, in step 4, Y can only assume the same value of X, or some other value that makes parents(Y, Z, W) true. Finally, you remove the cases where X == Y in step 5, which gives you a valid definition of sister_of(X, Y).

Difference between X\=Y and dif(X,Y)

What is the difference between this:
X \= Y
and this piece of code:
dif(X, Y)
I thought that they should behave the same, but they do not. Here's the example:
n_puta(L, N, X) :- nputa(L, N, 0, X).
nputa([], N, C, _) :- N = C.
nputa([G|R], N, C, X) :- G = X, nputa(R, N, Y, X), C is Y - 1.
nputa([G|R], N, C, X) :- dif(G,X), nputa(R, N, C, X).
And here are some calls:
?- n_puta([a,a,b,b,b], 2, X).
X = a ;
false.
?- n_puta([a,a,b,a,b,b], 3, X).
X = a ;
X = b ;
false.
X should be the atom that occurs exactly N times in the list L. If I replace dif(G, X) with G \= X, I don't get the expected result. Can someone tell me what is the difference between these two operators? Can I use anything else except dif(G, X)?
This example works prefectly in SWI-Prolog, but doesn't work in Amzi! Prolog.
dif/2 and (\=)/2 are the same as long as their arguments are ground. But only dif/2 is a pure relation that works correctly also with variables and can be used in all directions. Your example clearly shows that you should use dif/2 in this case, because you use your predicate not only to test, but also to generate solutions. The most widely used Prolog systems all provide dif/2.

Resources