Picat style matching

Yes, goal- and term expansion is something where you’d like to use =>, but as it is one huge predicate and traditionally it uses :- :frowning: I see that more as a design issue of term/goal expansion than something that needs to be fixed by changing =>. Expansion rules must be defined such that a predicate deals with a specific expansion purpose and can be imported/exported to/from modules. Than the user can decide what to use for each rule set. I have a design laying around for that that minimizes incompatibilities, but is (of course) still incompatible.

Note that for =, you can simply use the code, which deals with all of them. Note that the first argument to the expansion goals is never unbound.

goal_expansion(X=Y, Goal) :-
    unifiable(X, Y, Unifier),
    comma_list(Goal, Unifier).

@jfmc If there is a bug, I’d like to hear about it. Note that SWI-Prolog does not implement ?=>. AFAIK, Picat initially only had => and ?=>, but we still have (and Picat re-introduced AFAIK) :-.

Well… I was actually abusing the implementation so please do not consider this a bug. Something like:

'?=>'(p(a,X), ((true->true),X=1)). % hack, without (true->true) this clause is not working
'?=>'(p(a,X), ((true->true),X=2)).
p(b,X) => X=3.

produced spurious “no matching head” exceptions on backtracking for p(a,X). I can no longer reproduce this problem either you fixed it or I was doing something stupid.

1 Like

True; but I had this rule (which was wrong for the goal X=Y):

goal_expansion('#'(Acc1,Ops1,Prv1,Out1,Num1,Nsq1) =
               '#'(Acc2,Ops2,Prv2,Out2,Num2,Nsq2)), Goal) :-
    Goal = (Acc1=Acc2, Ops1=Ops2, Prv1=Prv2, Out1=Out2, Num1=Num2, Nsq1=Nsq2).

It’s easily fixed, using subsumes_term/2; but it’s also an easy mistake to make – your goal expansion for < in DCGs avoids this bug because it can use => (although I think it also should have a test for nonvar(Name)).

If I wanted to go that way, I’d use something like below. That would also be consistent with the likely future of expansion where you compose individual predicates doing a specific expansion rather than having a single multifile predicate. The single predicate doesn’t work well with modules and it is hard to control the order in which expansion steps take place.

goal_expansion(X=Y, Clauses) :-
    expand_unify(X,Y,Clauses).

expand_unify(...) =>
...

with one final clause

expand_unify(_,_,_) => fail.

It would be nice to have a “style guide” for writing term and goal expansions – nobody seems to really like the current design for term expansion, but until a better design happens, we could at least help avoid some of the pain.

For style guide: the top level goal_expansion/2 or term_expansion/2 should only match the clause or goal; it should delegate the expansion to another predicate, even if there’s only one clause in that predicate (this allows marking the clause as deterministic, so that an error is thrown when it fails instead of silently failing to expand. And one-way unification should be used wherever possible (it can’t be used at the top level because => doesn’t work with multifile predicates.