3

Let's define custom operators - let it be ++,equals

:- op(900, yfx, equals).
:- op(800, xfy, ++).

And fact:

check(A equals A).

I try to make predicate, let it be check/1, that will return true in all following situations:

check( a ++ b ++ c ++ d equals c ++ d ++ b ++ a ),
check( a ++ b ++ c ++ d equals d ++ a ++ c ++ b),
check( a ++ b ++ c ++ d equals d ++ b ++ c ++ a ),
% and all permutations... of any amount of atoms
check( a ++ ( b ++ c ) equals (c ++ a) ++ b),
% be resistant to any type of parentheses

return

yes

How to implement this in Prolog? (Code snippet, please. Is it possible? Am I missing something?)

Gnu-Prolog is preferred, but SWI-Prolog is acceptable as well.

P.S. Please treat code, as draft "pseudocode", and don't care for small syntax issues.

P.P.S '++' is just beginning. I'd like to add more operators. That's why I'm afraid that putting stuff into list might be not good solution.

Additionally

Additionally, would be nice, if queries would be possible (but, this part is not required, if you are able to answer to first part, it's great and enough)

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

one of possible results (thanks @mat for showing others)

X=c, Y=a

I am looking mostly for solution for first part of question - "yes/no" checking.

Second part with X,Y would be nice addition. In it X,Y should be simple atoms. For above example domains for X,Y are specified: domain(X,[a,b,c]),domain(Y,[a,b,c]).

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Grzegorz Wierzowiecki
  • 10,545
  • 9
  • 50
  • 88
  • 1
    Hi, you know you can "accept" answers by clicking the big checkmark, right? – Yuji Aug 29 '11 at 15:13
  • 1
    I would not use ++, this is often found for string concatenation. But string concatenation is not commutative. But what you show here by permutation is an effect of commutativity and associativity. –  Aug 31 '11 at 10:58
  • So, @countably-infinite what operators for 'custom' '+','*','/','^','-','mod' would you reccomend ? – Grzegorz Wierzowiecki Aug 31 '11 at 11:21
  • 1
    Operators in Prolog are anyway not evaluated by default. The only context they get evaluated is is/2, 2, etc.. or some CLP. So you can use the existing ones without harm. If youre intention is the arithmetic +, then use +, or if your intention is the arithemtic *, then you can use *, etc.. –  Aug 31 '11 at 12:35

2 Answers2

5

Your representation is called "defaulty": In order to handle expressions of this form, you need a "default" case, or explicitly check for atom/1 (which is not monotonic) - you cannot use pattern matching directly to handle all cases. As a consequence, consider again your case:

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

You say this should answer X=c, Y=a. However, it could also answer X = (c ++ d), Y = (a ++ d). Should this solution also occur? If not, it would not be monotonic and thus significantly complicate declarative debugging and reasoning about your program. In your case, would it make sense to represent such expressions as lists? For example, [a,b,c,d] equals [c,d,b,a]? You could then simply use the library predicate permutation/2 to check for equality of such "expressions".

It is of course also possible to work with defaulty representations, and for many cases they might be more convenient for users (think of Prolog source code itself with its defaulty notation for goals, or Prolog arithmetic expressions). You can use non-monotonic predicates like var/1 and atom/1, and also term inspection predicates like functor/3 and (=..)/2 to systematically handle all cases, but they usually prevent or at least impede nice declarative solutions that can be used in all directions to test and also generate all cases.

mat
  • 40,498
  • 3
  • 51
  • 78
0

This question is rather old, but I'll post my solution anyways. I'm learning prolog in my spare time, and found this quite a challenging problem.

I learned a lot about DCG and difference lists. I'm afraid, I didn't come up with a solution that does not use lists. Like mat suggested, it transforms terms into flat lists to cope with the parentheses, and uses permutation/2 to match the lists, accounting for the commutative nature of the ++ operator...

Here's what I came up with:

:- op(900, yfx, equ).
:- op(800, xfy, ++).

check(A equ B) :- A equ B.

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

sum_pp(Term, List, Len) :- sum_pp_(Term, List,[], 0,Len).

sum_pp_(A, [A|X],X, N0,N) :- nonvar(A), A\=(_++_), N is N0+1.
sum_pp_(A, [A|X],X, N0,N) :- var(A), N is N0+1.
sum_pp_(A1++A2, L1,L3, N0,N) :-
    sum_pp_(A1, L1,L2, N0,N1), (nonvar(N), N1>=N -> !,fail; true),
    sum_pp_(A2, L2,L3, N1,N).

The sum_pp/3 predicate is the workhorse which takes a term and transforms it into a flat list of summands, returning (or checking) the length, while being immune to parentheses. It is very similar to a DCG rule (using difference lists), but it is written as a regular predicate because it uses the length to help break the left recursion which would otherwise lead to infinite recursion (took me quite a while to beat it).

It can check ground terms:

?- sum_pp(((a++b)++x++y)++c++d, L, N).
L = [a,b,x,y,c,d],
N = 6 ;
false.

It also generates solutions:

?- sum_pp((b++X++y)++c, L, 5).
X = (_G908++_G909),
L = [b,_G908,_G909,y,c] ;
false.

?- sum_pp((a++X++b)++Y, L, 5).
Y = (_G935++_G936),
L = [a,X,b,_G935,_G936] ;
X = (_G920++_G921),
L = [a,_G920,_G921,b,Y] ;
false.

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G827++_G828),
L = [_G827,_G828],
N = 2 ;
Y = (_G827++_G836++_G837),
L = [_G827,_G836,_G837],
N = 3 .

The equ/2 operator "unifies" two terms, and can also provide solutions if there are variables:

?- a++b++c++d equ c++d++b++a.
true ;
false.

?- a++(b++c) equ (c++a)++b.
true ;
false.

?- a++(b++X) equ (c++Y)++b.
X = c,
Y = a ;
false.

?- (a++b)++X equ c++Y.
X = c,
Y = (a++b) ;
X = c,
Y = (b++a) ;
false.

In the equ/2 rule

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

the first call to sum_pp generates a length, while the second call takes the length as a constraint. The cut is necessary, because the first call may continue to generate ever growing lists that will never again match with the second list, leading to infinite recursion. I haven't found a better solution yet...

Thanks for posting such an interesting problem!

/Peter

edit: sum_pp_ written as DCG rules:

sum_pp(Term, List, Len) :- sum_pp_(Term, 0,Len, List, []).
sum_pp_(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_(A1++A2, N0,N) -->
    sum_pp_(A1, N0,N1), { nonvar(N), N1>=N -> !,fail; true },
    sum_pp_(A2, N1,N).

update:

sum_pp(Term, List, Len) :-
    (    ground(Term)
    ->   sum_pp_chk(Term, 0,Len, List, []), !  % deterministic
    ;    length(List, Len), Len>0,
         sum_pp_gen(Term, 0,Len, List, [])
    ).

sum_pp_chk(A, N0,N) --> { A\=(_++_), N is N0+1 }, [A].
sum_pp_chk(A1++A2, N0,N) --> sum_pp_chk(A1, N0,N1), sum_pp_chk(A2, N1,N).

sum_pp_gen(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_gen(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_gen(A1++A2, N0,N) -->
    { nonvar(N), N0+2>N -> !,fail; true }, sum_pp_gen(A1, N0,N1),
    { nonvar(N), N1+1>N -> !,fail; true }, sum_pp_gen(A2, N1,N).

I split sum_pp into two variants. The first is a slim version that checks ground terms and is deterministic. The second variant calls length/2 to perform some kind of iterative deepening, to prevent the left-recursion from running away before the right recurson gets a chance to produce something. Together with the length checks before each recursive call, this is now infinite recursion proof for many more cases than before.

In particular the following queries now work:

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G1515++_G1518),
L = [_G1515,_G1518],
N = 2 .

?- sum_pp(Y, [a,b,c], N).
Y = (a++b++c),
N = 3 ;
Y = ((a++b)++c),
N = 3 ;
false.
Peter Remmers
  • 1,273
  • 12
  • 11
  • Consider using a DCG for `sum_pp_` to describe the list of elements, for example: `sum_pp_(A) --> { nonvar(A), A \= (_++_)}, [A].`. – mat Nov 30 '13 at 11:53
  • I had exactly this during "development". But then I went back to regular rules because of the length parameters and because you can't address the hidden parameters directly. When I looked at the generated rules, I found that it generated a call to `phrase/2` which I didn't want. That was when I tried using `length/2` to generate lists for variables. Also, I still don't quite get why there's a need for `phrase/2` and what the difference is to calling the rules directly. – Peter Remmers Nov 30 '13 at 15:09
  • `phrase/3` differs notably from `call/3` in how conjunctions are processed: for example `phrase(nt, Ls0, Ls)` may be equivalent to `nt(Ls0, Ls)` and hence to `call(nt, Ls0, Ls)`, but `phrase((a,b), Ls0, Ls)` is of course not the same as `call((a,b), Ls0, Ls)` because *comma* in DCGs can be read as "and then" and must be handled differently for DCGs. The nonterminal `Goal` can thus not be translated to `call(Goal, Ls0, Ls1)`, but must be translated to `phrase(Goal, Ls0, Ls1)`. It is often useful to use pattern matching to restrict how long the lists can grow (`[_|Rest]`) instead of arithmetic. – mat Nov 30 '13 at 16:52
  • So, AIUI, in this particular case, `phrase/3` is not needed, because as you say, it is equivalent to calling a DCG rule directly. It is only needed because you can give it a bare DCG _body_, and that has to be `expand_term`ed before it can be called. Right? – Peter Remmers Nov 30 '13 at 17:55
  • As for pattern matching vs. arithmetic: How would you do this here? I mean, the lists that are involved are not accessible because they are hidden parameters in the DCG rules... – Peter Remmers Nov 30 '13 at 18:42
  • I am saying the opposite: You need to use the `phrase/3` or `phrase/2` interface to DCGs, since (1) you should not rely on a particular expansion for DCGs (different Prolog systems may expand DCGs differently to regular Prolog) and (2) calling a DCG construct is not the same as calling a Prolog goal. Regarding arithmetic, you may be able to use lists instead of integers to indicate the number of tokens. – mat Nov 30 '13 at 19:04
  • Thanks @PeterRemmers for this wide post and explanation. Quesiton is old, but still I am very newbe level in prolog :). I've tried to play with your snippets to learn, but unfortunately I've encountered, following error: "uncaught exception: error(existence_error(procedure,(:-)/2),top_level/0)" with even first snippets, here is full log: http://pastebin.com/wdGBXu69 , may I ask for advice? I am using GNU Prolog 1.4.4, maybe you are using other. – Grzegorz Wierzowiecki Feb 09 '14 at 09:44
  • I use SWI Prolog. My guess is that the definition of the custom operators 'equ' and '++' probably did not work. From what I see in your pastebin, I think you forgot to use (:-)/1 to define them? Sorry, I don't know much about GNU prolog. – Peter Remmers Feb 25 '14 at 14:55