2

I need to match one pattern with several different terms in Prolog, but I don't want to unify any of the variables when matching them. I found one possible way to do this, but it seems inefficient:

:- initialization(main).
:- set_prolog_flag('double_quotes','chars').

main :-
    Pattern = (A>B;B<A),
    match_without_unify(Pattern,(1>A1;A1<1)),
    match_without_unify(Pattern,(2>4;4<2)).

match_without_unify(A,B) :-
    %copy the terms, unify them, and count the number of variables
    copy_term([A,B],[A1,B1]),
    term_variables(B1,L1),
    length(L1,L),
    A1=B1,
    term_variables(B1,L2),
    length(L2,L),
    writeln([L1,L2]).

Is it possible to solve this problem without calling term_variables/2 and length/2 twice?

false
  • 10,264
  • 13
  • 101
  • 209
Anderson Green
  • 30,230
  • 67
  • 195
  • 328
  • When exactly do you consider something a match? Is `A =?= 2` a match? or should it be `A =?= B`? – Willem Van Onsem Dec 25 '19 at 20:20
  • @WillemVanOnsem If `A` is a free variable, then `match_without_unify(A,2)` succeeds, but `match_without_unify(2,A)` fails. It works as intended. – Anderson Green Dec 25 '19 at 20:23
  • 1
    You can test if a match is possible with [subsumes_term/2](http://www.complang.tuwien.ac.at/ulrich/iso-prolog/dtc2#subsumes_term). – false Dec 25 '19 at 21:01
  • 1
    BTW, your definition is most probably incorrect: Instead of taking the length, better test `L1 == L2` – false Dec 25 '19 at 21:07
  • 1
    Here is a minimal counterexample: `match_without_unify(-X,Z).` succeeds incorrectly. But I presume you want it to fail. Note that `subsumes_term(-X,Z).` fails as you probably expect. – false Dec 25 '19 at 21:16
  • @false Yes, it seems to work correctly when I test `L1 == L2` instead of using `length/2`. `subsumes_term/2` is probably the predicate that I was looking for. – Anderson Green Dec 25 '19 at 22:55

2 Answers2

3

What you need here is called syntactic one-sided unification. This is provided via the ISO-builtin subsumes_term(General, Specific) which

... is true iff there is a substitution θ such that

a) Generalθ and Specificθ are identical, and
b) Specificθ and Specific are identical

Note that the notion of matching is usually only defined with Specific being ground. In such a context it suffices to demand that

Generalθ and Specific are identical

There are various ways how this kind of matching may be extended, but most of the time the fine print is neglected as in the case when the condition of Specific being ground is simply discarded. This leads to quite unexpected behaviour since General = X, Specific = s(X) now "match", with θ = {X → s(X)}.

In (old) systems that do not provide subsumes_term/2, it can be easily implemented like so:

subsumes_term(General, Specific) :-
   \+ General \= Specific, % optimization, which sometimes makes it slower ...
   \+ \+ (  term_variables(Specific, Vs1),
            General = Specific,
            term_variables(Specific, Vs2),
            Vs1 == Vs2
         ).

A faster implementation may avoid full unification and the creation of the variable lists. This implementation requires rational tree unification. Replace \= and = using unify_with_occurs_check/2 in an ISO-implementation that does not support rational trees.

false
  • 10,264
  • 13
  • 101
  • 209
1

Your question has an affirmative answer:

solve this problem without calling term_variables/2 and length/2 twice?

A fastfail solution is found in an utility. The solution by @false has the drawback that
it calls General = Specific, which might unneccessarely perform a full unification
also in cases where pattern matching fails.

subsumes(X, Y) :-
    term_variables(Y, S),
    subsumes(X, Y, S).

subsumes(X, Y, S) :- var(X), member(V, S), V == X, !,
    X == Y.
subsumes(X, Y, _) :- var(X), !,
    X = Y.          %  binds
subsumes(X, Y, S) :-
    nonvar(Y),          %  mustn't bind it
    functor(X, F, A), functor(Y, G, B),
    F/A = G/B,
    X =.. [_|L],
    Y =.. [_|R],
    subsumes_list(L, R, S).

subsumes_list([], [], _).
subsumes_list([X|L], [Y|R], S) :-
   subsumes(X, Y, S),
   subsumes_list(L, R, S).

Loosly after Richard O'Keefes:
http://www.picat-lang.org/bprolog/publib/metutl.html