Writing code that does rewrites of terms that may contain variables, I recall the pain doing such. One always has to be careful not to instantiate the input data. Thus,
rule(Var, Out) :-
var(Var),
!,
Out = Var.
rule((A0,B0), (A,B)) :-
rule(A0, A),
rule(B0, B).
...
This is sort of bearable. As the term in the head gets more complicated though, you need more and more var/nonvar/compound/etc. tests or you can write something as below
rule(In, Out) :-
subsumes_term(f(g(X),h(y)), In),
In = f(g(X),h(y)),
...
And, as the patterns contain lots of variables you typically have to use a cut to commit. Picat has the =>
notation for the neck (:-
), which specifies one sided unification. Given that the head term and the argument do not share sub terms, this is semantically that same as the subsumes_term/2 example above, followed by a cut.
Although I do not like many aspects of Picat, I do like this (I notably dislike giving up the program-is-data aspect of Prolog). I once wrote a small prototype that implements => and ?=> in the VM. It keeps clause indexing mostly in place (the indexing could be more selective).
The general form of a Picat rule is Head, Condition => Body
, where the Head
is matched using one-sided unification and the =>
acts as :-, !
(there is also a variant ?=>
that does the same unification but without implicit cut. I’m not sure we need that).
Note that this Picat style forces steadfast code as we cannot unify before the !, so broken rules like this do not work at all, which is better than Prolog where it works “somewhat”:
max(X,Y,X) :- X >= Y, !.
max(_,Y,Y).
You need to write the code instead, which is exactly what we want users to write.
max(X,Y,M),X>=Y => M = X.
max(_,Y,M) => M = Y.
I’ve seen so many people making mistakes that are avoided using =>
that I’m tempted to add this to SWI-Prolog. In that case I also propose to define that, if no rule matches, it is an error (you can always add a catch-all rule). That would
- Make writing (semi) det code much less error prone: always steadfast, always deterministic clause selection and an error if no rule matches.
- Make writing rewrite rules for non-ground terms much easier and much more efficient because clause indexing keeps doing its work.
This has very few consequences. The additions are simple and short. The tools are involved in some places, but nothing too serious I suspect. The only other problem I see is clause/2. We could have an alternative that has the full rule instead of the body as second argument:
?- rule(max(_,_,_), C).
C = max(X,Y,M),X>=Y => M = X ;
C = max(_, Y,M) => M = Y.
Picat seems to call these rules. The main disadvantage I see is that rule
is not unlikely to clash with user code.