Would it be possible to implement a second SSU Operator "*=>" which allows for backtracking in the left hand side?

Currently I have to write

p008(X), (var(X) ; between( 0, 5, X)) => between( 0, 5, X) .

but I would like to write:

p008(X), between( 0, 5, X) *=> true.

I find the SSU operator ‘=>’ very useful. Is this actually a thing in logtalk?

Regards,
Frank Schwidom

Picat, from which => comes, has *=>, but I think it implies that subsequent clauses are still considered. That you seem to want is something that commits to the clause, but maintains non-determinism of the guard?

That said, I’d write

p008(X), var(X) => between(0,5,X).
p008(X) => between(0,5,X).

Depending on the other clauses as the above is simply

p008(X) :- between(0,5,X).

The overall idea behind => is to provide a deterministic switch that is not subject to steadfastness issues.

In my examples p008(6) would fail with an error.

?- p008( 6).
ERROR: No rule matches p008(6)
ERROR: In:
ERROR: [12] p008(6)
ERROR: [11] toplevel_call(user:user: …) at /usr/local/lib/swipl/boot/toplevel.pl:1319

This is not the case in your examples.

(ins)?- p008( 6).
false.

This is not what I want to get.

In case we want to implement it I can also live with another operator symbol.

Actually the Picat is ?=> I think. The more principled question is whether or not we want non-determinism in the guard. It feels counter intuitive to me. Opinions? Existing current practice in similar systems?

Could just use a standard predicate i.e. :-, with !/0 where appropriate (and maybe even throw/1), to have all the desired flexibility.

Personally, I never use SSU.

OK, since it’s clear that not everyone is immediately convinced by the idea of the *=> operator, let me do a bit of promotion for it.

First, I find the => operator already quite compelling because it places a type check at the start of a predicate, which is very straightforward to write since I don’t need to use the must_be notation. Additionally, it allows me to easily extend the scope of a predicate by simply adding more rules.

Of course, the :- operator could achieve similar results if you accept a logical false instead of an error, but this often leads to silent and hard-to-debug failures.

From a software development perspective, I personally prefer receiving a hard error when my domain boundaries are clearly violated, rather than dealing with a silent failure that’s tricky to track down.

Moreover, the => operator makes it possible to create new predicates by combining existing ones, as illustrated in the following example:

p001(I, O), var(I) => O = var.

p002(I, O), nonvar(I) => O = nonvar.

catchmatch(GOAL) :- catch(GOAL, error(existence_error(matching_rule, _), _), false).

p003(I, O), catchmatch(p001(I, O)) => true.
p003(I, O), catchmatch(p002(I, O)) => true.

Now, imagine that the predicates p001 and p002 can produce multiple results. The *=> operator in rule p003 could simplify things significantly: you get all the results from p001 and p002 without extra effort, while still actively enforcing domain constraints.

p003(I, O), catchmatch(p001(I, O)) *=> true.
p003(I, O), catchmatch(p002(I, O)) *=> true.

Frank Schwidom

But a problem keeps remaining. What if I have a legit false from for instance p001 (from the body) it is then interpreted as a domain violation in p003. Maybe I’m wrong with my idea. Lets sleep 1 night about that.

1 Like

Good plan :slight_smile: I’m mostly interested in what others think about this. Note that, AFAIK, CHR (another committed-choice language) does not allow for non-determinism in the guard. Also, you consider the rules “type (domain) checked”. That is a reasonable view, but non-determinism and (type) checking are in different parts of my brain :slight_smile:

Here is my opinion (I think this has come up before).

For the current SSU operator, the guard must have the “@” mode for variables shared with the head; that is:

Argument will not be further instantiated than it is at call-time.

I know this is a very stringent requirement. Some of it probably can’t be checked at compile time, for example:

foo(A, B), between(1, 3, A) => B is 0 - A.

Currently, this works (or throws) with a nonvar A; but it also just works with a free variable in A. But surely it looks like a programming error?

At least for me, if => worked like this, it is easier to comprehend the meaning of relaxing the requirement for the guard.

1 Like

That is how I intended it and it is a requirement to guarantee steadfastness. Unfortunately I see no reasonable way to enforce that, so -as is- the guard is just a arbitrary Prolog goal …

I guess the question arises whether we should embrace arbitrary goals or stick with the original intend and consider guards that bind the goal arguments “bad”? I must admit that I (mis)use the guard occasionally.

I think there are four key properties to SSU:

  • Simplify pattern matching on arguments that may contain variables.
  • Guarantee steadfastness.
  • Get an error if no rule matches.
  • Encourage clean usage of commits (cuts).

Allow arbitrary guards destroys the second, harms the 4th and preserves the other two properties.

Hello,
Here is just another data point for this discussion.

I have recently managed to get SSU click in my head and use it extensively, e.g. for parsing terms or expressions.
The one thing I rely on very strongly is the guaranteed steadfastness (nothing will be bind in the head or guard).

In my opinion, relying on the generic error from SSU is not a good way to do type checking.
It works but is not the “correct” solution.

My own take on your first example:

p008(X) :-
  (  between(0, 5, X)
  -> true
  ;   domain_error(X, between(0, 5))
  ).

I know it is more verbose, but worth it in my opinion.

But this is not the case in the current implementation. This example:

p007(X), between( 0, 5, X) => between( 0, 5, X) .

gives me the following result:

?- p007(X).
X = 0.

and this:

p005(X), between( 0, 5, X) => true.

the same:

?- p005( X).
X = 0.

So the guard binds values and hinders recursion in the LHS. Maybe it is the same in picat, I didn’t check that.

Isn’t this just a “pending” known bug of the current implementation ?
The documentation says, this is just a quirk of the implementation and you should not do it:

Though not enforced, guard code shall not instantiate variables in the Head because this breaks the promise of SSU and may make the node non-steadfast.

In my case, I forgot about this in order to retain steadfastness (which is important to me).

– edits–
There is even a little note in the implementation explaining that the guard is not checked because of thechnical difficulties

Sorry, with “current implementation” I meant my installation. I have currently

Welcome to SWI-Prolog (threaded, 64 bits, version 9.3.20-DIRTY)

installed. Maybe it behaves different in later versions.

I got it - it has nothing to do with the version. So I should write instead of p005 and p007:

p00B(X), \+ \+ between( 0, 5, X) => between( 0, 5, X).

Then I get:

(ins)?- p00B(6).
ERROR: No rule matches p00B(6)
ERROR: In:
ERROR: [12] p00B(6)
ERROR: [11] toplevel_call(user:user: …) at /usr/local/lib/swipl/boot/toplevel.pl:1319
(ins)?- p00B(3).
true.

(ins)?- p00B(X).
X = 0 ;
X = 1 ;
X = 2 ;
X = 3 ;
X = 4 ;
X = 5.

double negation is a nice trick, but expensive.
Another solution is testing explicitly what you want:

p00B(X), (var(X) ; 0 =< X, X =< 5) => between(0, 5, X).

If the guard become too long or heavy, you can always refactor into another predicate.
–edit–
Just a little thought here: using SSU (and achieving steadfastness) is the process of separating that small imperative portion of your code from the general “messy” pure non-deterministic prolog code ^^