Do the Picat operators `=>` and `?=>` have a formal name in SWI-Prolog?

Since SWI-Prolog is now incorporating the Picat operator =>/2 I was wondering if it has a formal name for use in SWI-Prolog?

I checked the Picat User’s Guide and the closest I found was in

Chapter 4

Predicates and Functions

Picat has two types of pattern-matching rules: the non-backtrackable rule

Head, Cond => Body.
and the backtrackable rule

Head, Cond ?=> Body.

and later in

Appendix I

Appendix: Grammar

predicate_rule ->  
    head ["," condition] ("=>" | "?=>") body eor  
    head (":-" | "-->") body eor  
 
nonbacktrackable_predicate_rule ->  
    head ["," condition] "=>" body eor  

In the tutorial it notes

Core logic programming concepts

Implicit pattern-matching and explicit unification

I tend to find Implicit pattern-matching more descriptive than => but at present I take it that, that would still include both the backtracking and not-backtracking variations.

The other interesting item of note upon first reading is that Picat also uses the name predicate with => or ?=>.

Anyway, without a proper unique name for the operators in SWI-Prolog, this could become very confusing when trying to understand this when a less informed person ask a question about => or ?=> and does not specifically use the proper syntax for the operators.

So

Do the Picat operators => and ?=> have a formal name in SWI-Prolog?


EDIT

As I write this I see that Jan W. is also writing a reply.

The SWI-Prolog manual does use the name Single Sided Unification rules so I guess that will be the formal name for => and ?=> when used with SWI-Prolog. Still interested in what Jan W. notes.

Not yet. Well, for now I want to keep ?=> out because we have classical Prolog clauses if you want non-determinism. This could of course change. A good name is surely welcome. For now I normally refer to them as SSU (single side unification) rules. As they also commit this is still not ideal terminology. Ideas are welcome …

1 Like

What if you want non-determinism and SSU?

From source inspection it seems this combo exists, and (=>)/2 is
even mapped to (?=>)/2, only there is no syntax operator for it:

'$store_clause'((Pre => Body), _Layout, File, SrcLoc) :-
    nonvar(Pre),
    Pre = (Head,Cond),
    !,
    '$store_clause'(?=>(Head,(Cond,!,Body)), _Layout, File, SrcLoc).
[...]
conditional_rule(?=>(Head, Body0), (Head,Cond=>Body)) :-
    split_on_cut(Body0, Cond, Body),
    !.
conditional_rule(Rule, Rule).

https://\github.com/SWI-Prolog/swipl-devel/commit/f24b5cf43d6719296ffe21608b976cd66ca7d2aa

But (?=>)/2 is not exactly as in Picat, I guess head condition is missing.
But probably there is this equivalence. This here.

Head, Cond ?=> Rest.

Is the same as this here:

Head ?=> Cond, Rest.

So renormalizing into the second form wouldn’t harm
anybody if they are equivalent. Have to check with Picat.

Glad to hear that Single Sided Unification rules is not set in stone at this point.

While I don’t have a name to propose at this time, here are some of the thoughts I have when contemplating a name. I am writing this so that others can have a litmus test to see if their thoughts are similar to others, or at least mine at the moment.

Note: I have not used Picat or even used => in SWI-Prolog yet so this is all based on just what I have read.

If we exclude the backtrackable rule (?=>) for now and only consider the non-backtrackable rule (=>) then the properties to consider are

  1. Is a pattern matching (Pattern matching programming languages)
  2. Is not backtracking which I will take to mean that it is either determinate or fails.
  3. Is not unification so is one way, thus the syntax for the operator =>.
  4. Allows conditions on the left of the operator. While it does remind me of forward chaining, as Jan W. notes, => is not forward chaining. I wish I had practical experience with something similar in another programming language so that I can relate but do not. The only thing I know of that adds some clarity to adding conditions on the left of the operator is (ref)
p(V1,V2,...,Vn) :-
    Pattern = p(A1,A2,...,An),
    Args = p(V1,V2,...,Vn),
    subsumes_term(Pattern, Args),
    Pattern = Args,
    Guard,
    !,
    Body.
  1. Pattern matching also reminds me of Algebraic Data Types but I don’t know how this relates to => but understanding the relation between Algebraic Data Types and => could be used as a discriminator for understanding.
  2. This also reminds me of Modus ponens. Something more to be used as a discriminator for understanding.
  3. The description for Picat uses the phrase Implicit pattern-matching but does the adjective Implicit have meaning with the SWI-Prolog operator =>?
  4. Is like a rabbit hole in that once you use => for a predicate or rule (which I am using liberally at present) you are committed to using it for the remainder of the predicate or rule. (ref)

A predicate either uses :-/2 for all its clauses or =>/2 . Mixing is not allowed and raises a permission error for a clause that does not use the same neck as the first clause.

  1. Results in errors instead of silent failures. (ref). Which IMHO takes us out of the land of logic and into the land of procedural programming and possibly functional programming.

Unlike Picat, it is an error if no clause matches.


At present the idea that keeps popping into my head about this is F# pattern matching. When I get some time I will do comparisons as test cases but this is not even on the short list.

The other thing that keeps popping into my head about this is that one should look at ways to track the errors yet continue processing, think monads. In my mind once one starts using the SWI-Prolog operator => they are going to want a way to work with the errors that does not kill the process.

In F# the answer is Railway Oriented Programming, in Prolog the closest thing I know is DCGs or open list.


If others are wondering why I seem to have a fixation with a name for this operator it is that if you don’t have a meaningful and easily searchable name for something, it makes it much harder to learn about it and if you can’t learn about something because you can not find information about something, then it will be lost to the sands of time. Have you ever tried to search for the . operator when first learning about object-oriented modules, knowing the name makes it so much easier.

I have never seen “implicit” in Picat documentation in connection with “pattern-matching”.
Picat refers to “implicit” in connection with “backtracking”.

Picat laments that in Prolog rules or facts backtracking is implicit, and wants avoid that by having (=>)/2 (no backtracking) and (?=>)/2 (backtracking).

But both (=>)/2 and (?=>)/2 use the same pattern matching aka single sided unification. You can check yourself the Picat documentation:

Picat, like Prolog, supports backtracking. In Prolog, every clause is implicitly backtrackable, and the cut operator ! can be utilized to control backtracking. In contrast, backtrackable rules in Picat must be explictly denoted, which renders the cut operator unnecessary. Consider the predicate member/2 , as defined in Prolog and in Picat:

% Prolog 
member(X,[X|_]). 
member(X,[_|T]) :- member(X,T). 

% Picat 
member(X,[Y|_]) ?=> X = Y. 
member(X,[_|T]) => member(X,T).

http://picat-lang.org/download/picat_compared_to_prolog_haskell_python.html

Sorry to jump in the middle here, but => is normally used as logical implication operator.
This is the case for instance in https://www.cpp.edu/~jrfisher/www/prolog_tutorial/logic_topics/geolog/geoprolog.pl which is now broken.
It is also broken in my recent work on SEcond EYE seeye/seeye.pl at master · josd/seeye (github.com) which is supporting PREM => CONC formulae like used in seeye/graph.pl at master · josd/seeye (github.com) or seeye/dpe.pl at master · josd/seeye (github.com)

I suggested a fix already, i.e. module system:

Thanks for raising. To some extend this is of course unavoidable. Any operator we add is not unlikely to be in use by someone somewhere. This means that adding a feature like this either requires some weird ugly operator nobody ever uses or using modules as @j4n_bur53 would like to see.

I’m not (yet) a fan of requiring a module to support => rules. If this evolves into something that is heavily used you don’t want to import a library for it all over the place.

The operator is not the problem. You can just redefine this and any application using => will have to define this. The problem is that X=>Y is now compiled into something different than it used to. You can fairly easily work around that using term_expansion/2 to translate it into something else. Of course, that means => means something different in your context.

I’m not against using another operator either. Picat has been thinking about this before though …

Does SWI-Prolog have an un-expansion framework? Frome code inspection I saw that the introduction ofn (=>)/2 needed changes all over the place. For example here:

I got an un-expansion framework in my system. But its kind of a low-speed track, and by the current introduction of closure expansion, I even don’t know whether it still works. But its just the analogue to expansion, only the other direction. It is for example used in listing/[0,1] and I described it already years ago as:

There are situations where the compile-time heuristics have to be undone to make them trans-parent. For example when listing clauses or debugging goals. The predicates rebuild_term/2 respectively rebuild_goal/2 are responsible for undoing expansions and simplifications. The rebuilding uses the same flags as the expansion and as well customizable via term_rebuilding/2 respectively goal_rebuilding/2.

http://www.jekejeke.ch/idatab/doclet/prod/en/docs/05_run/10_docu/05_frequent/07_theories/13_experiment/07_simp.html

Since it is in a module from the package “experiment” it can be seen that it is, well experimental. Maybe should harden the whole thing in the next time, there seems to be critical mass now to deploy such a framework.

If different Prolog systems would use the same frameworks, then a DSL like single sided rules with Picat syntax could become Prolog capsules, that can be deployed to different Prolog systems, like Tau Prolog, Scryer, etc…

Unexpansion is not really there. Well, listing/1 and portray_clause/1-3 can be hooked (used by e.g., Logtalk) and clause_info/5 used to get access to position information, variable names and environment stack layout to drive the graphical debugger can be hooked to make it handle conversions done by the rewrite system and compiler optimization. I’m not too happy with all of this. In the end, reverting these rewrites is complicated and often involves heuristics and assumptions and thus not always produce the correct result.

Syntax yes. You can do that already. You also need support in the VM to make it usable though. You either need to implement SSU directly as an alternative matching strategy for the head or you need transformation to use subsumes_term/2 and something clever in the compiler and VM to recognise this pattern and do something smart with it.

Yes, was also thinking about that, that maybe in the vincinity of portrayXXX other Prolog systems already offer something. Now I found a new potential clash. λProlog has (=>)/2 for hypothetical reasoning. The use of (=>)/2 for single sided unification clashes maybe with λProlog.

How about ‘=>>’ ? The two ‘>’ could be mnemonics for the two special properties:

  1. One way unification
  2. The rule commits and no other rules will be tried if condition and head match.

I think it is important to keep SWI-Prolog compatible with older code, code that users can’t change, most especially because they are just users who expect things to “simply work”. If we use ‘=>>’ or even ‘==>>’ there is much less probability of breaking existing code.

1 Like

I had a look at this. It isn’t broken due to =>, but due to the fact that old versions allowed for e.g. dynamic(exists(a)), while new versions require dynamic(exists/1) as required by The Standard (changed after some reports where the ambiguity of the old implementations caused too much confusion) . The =>/2 is no problem as the operator is (re)defined and the theory holding => terms is read and translated into something different.

It is also still possible to have clauses for a predicate =>/2, but you need to write them as

 (a=>b) :- true.

After which

?- X=>Y.
X = a,
Y = b.

So, in general this should suffice to load code using => as facts (rules work anyway).

term_expansion((X=>Y), ((X=>Y):-true)).

I thought this was a show-stopper. It seems it is not that bad though. It is a bit comparable to most of the other Prolog operators that you can redefine and overrule their predicate if you want. It may lead to some confusion if people are also allowed to write normal Prolog code in the same context.

1 Like

Can you have mixed clauses? One clause with normal unification and another clause with pattern matching, and then one clause with normal unification. All for the same predicate. Or is it a predicate property what kind of unification is used.

In my system I wouldn’t have any problem for example introducing a soft rule operator (:-*)/2. Similar like a soft cut (*->)/2. And then this here:

p(f(_)) :- write(foo), nl.
p(f(_)) :-* write(bar), nl.
p(f(_)) :- write(baz), nl.

Would be normal unification, pattern matching, normal unification. Subsequently:

?- p(X), fail; true.
foo
baz

I didnt find some usage of (:-*)/2 already. You could still provide some back and forth translation to (=>)/2 and (?=>)/2. But (:-*)/2 isn’t in the spirit of a 3-partite rule, i.e. Head, Guard ?=> Body. Its sill a 2-partite rule. The advantage is as follows:

  • assertz/1 and asserta/1: Are relatively easy to additionally also handle (:-*)/2.
  • retract/1: Dito, can also easily additionally handle (:-*)/2.
  • clause/2: Here the information disappears. I don’t know yet what to do.
    I rather though that when the information whether (:-)/2 or (:-*)/2 is needed,
    clause/3 is used, and the choice between (:-)/2 or (:-*)/2 is a
    clause reference property.

In my current expansion prototype clause/2 has no problem. Since we would see a subsumes/2 statement in the front of the body. But if single sided unification is not just syntactic sugar, but goes deeper, then its difficult to solve the clause/2 problem.

Maybe the right way is to implement clause/2 that it filters (:-*)/2. And rule/2 would give (:-*)/2. But I rather intend not to provide a new rule/[2,3], since I already have clause/3 (actually its named clause_ref/3), and I also have reference properties.

Whats also not clear, are retract/1 and clause/2 respectively supposed to use subsumes/2 itself during retrieval, or are they based on normal unification even for (:-*)/2. We cannot draw on Picat here, since it doesn’t have dynamic prediates.

But does there even exist something like dynamic predicates with a different semantic than normal unification?

Now I see and am happy that Seeye is now also running with swipl
thanks to your hint to use term_expansion((X=>Y), ((X=>Y):-true)).
which is now used in seeye/seeye.pl at master · josd/seeye (github.com)
The test results for josd/seeye: Second eye - Seeye (github.com)
using ./test swipl look fine Seeye with SWI Prolog · josd/seeye@9944af1 (github.com)

Thanks!

2 Likes

From the manual:

A predicate either uses :-/2 for all its clauses or =>/2 . Mixing is not allowed and raises a permission error for a clause that does not use the same neck as the first clause.

Mixing is IMO not desirable and makes the error in case no rule matches rather awkward. You should really see these things as different from normal Prolog predicates. They help implementing pretty common Prolog predicates that are not much in Prolog’s spirit but typically needed to make applications work, notably single moded computations from some data structure.

See rule/2. Not very happy with the name (which connects to the lack of a name for such predicates/clauses anyway). Note that rule/2 has a different interface that can be extended to support any new type of rule and also deals with normal clauses.

As is, they do normal unification. I have often felt the need for something different, either subsumption or variant. Just consider

 p(_).
 p(a).

How am I going to retract p(a) in standard Prolog? In SWI-Prolog you can do this (or simply X == a, but this show the more general pattern). This approach is far from ideal as it looses clause indexing.

?- clause(p(X), true, Ref), p(X) =@= p(a), erase(Ref).

I guess this is best handled using variations on clause/2 and retract/2. Hmm. The rule/2,3 API could fix this. In the current implementation it merely uses the Head for getting the predicate. We could only return rules that unify without binding any variable in Head. So, you would get

?- rule(p(a), Rule).
 Rule = p(_) ;
 Pule = p(a).

but not a possible p(b). That allows for clause indexing, after which you can filter using =@=/2 or subsumes_term/2.

In some sense clause/2 could just return either the (:-)/2 or (:-*)/2 body in its 2nd argument and at the same time apply either normal unification or pattern matching respectively in its 1st argument. Then a meta interpreter can be written as follows:

solve(true).
solve((A,B)) :- solve(A), solve(B).
solve(H) :- clause(H,B), solve(B).

So this would make for a new semantic of clause/2, a semantic that reflects execution and not inpection. The new semantic is backward compatible to (-:)/2. Alternatively instead of (:-)/2 or (:-*)/2, was also thinking about a marker '!*'. So the example could be written:

p(f(_)) :- write(foo), nl.
p(f(_)) :- '!*', write(bar), nl.
p(f(_)) :- write(baz), nl.

Then to reflect execution, clause/2 could return:

?- clause(p(X), Y).
X = f(_), Y = write(foo), nl.
X = f(_), Y = write(baz), nl.

And for inspection rule/2 could keep the Head and Body format, and return:

?- rule(p(X), Y).
X = f(_), Y = write(foo), nl.
X = f(_), Y = '!*', write(bar), nl.
X = f(_), Y = write(baz), nl.

Because now the information whether a rule uses normal unification or pattern matching is always coded in the body by the marker ‘!*’. So there is no need for rule/2 that returns a Rule in its 2nd argument.

The marker '!*' might look strange. But it would be used by (=>)/2 and (?=>)/2 rewriting, and therefore hardly seen by the end-user. The listing/[0,1] predicate would use rule/2 and make the marker '!*' also disappear. clause/2 and meta-execution wouldn’t see it either.

Only for the ultra currious spies, there is rule/2. And maybe assertz/2 and retract/2 would also see it that way. Whereas the vanilla interpreter would be a use case for execution semantic of clause/2, I dont know yet about exection semantic for assertz/2 and retract/2,

they would receive the inspection semantic like the new rule/2.

The idea wasn’t so far fetched, I find:

4.6 Matching
In ECLiPSe you can write clauses that use matching (or one-way unification) instead of > head unification. Such clauses are written with the ?- functor instead of :-. Matching has > the property that no variables in the caller will be bound. For example

p(f(a,X)) ?- writeln(X).

will fail for the following calls:

?- p(F).
?- p(f(A,B)).
?- p(f(A,b)).

and succeed (printing b) for

?- p(f(a,b)).

ECLiPSe - A Tutorial Introduction
Page 47 (Logical 37)
http://eclipseclp.org/doc/tutorial.pdf

Under the hood ECLiPSe Prolog uses also a marker in the body, but not a marker like the '!*' I am suggesting, but a prefix operator:

[eclipse 6]: clause(p(X), Y).

X = f(_209)
Y = (write(foo), nl)
Yes (0.00s cpu, solution 1, maybe more) ? ;

X = f(_209)
Y = (-?-> write(bar), nl)
Yes (0.00s cpu, solution 2, maybe more) ? ;

X = f(_208)
Y = (write(baz), nl)
Yes (0.00s cpu, solution 3)
[eclipse 7]: current_op(X,Y,-?->).

X = 1180
Y = fx
Yes (0.00s cpu, solution 1, maybe more) ? ;

No (0.00s cpu)

But dynamic calls, I made p/1 dynamic to inspect it, don’t work correctly:

[eclipse 2]: p(X), fail; true.
foo
calling an undefined procedure -?-> write(bar), nl in module eclipse
Abort

And clause/2 is closer to rule/2, not suitable for solve/2.