2

I'm new to Prolog and logic programing in general, I'm writing a small theorem prover for fun, and in doing so I wrote a normalization procedure. I wanted this procedure to be deterministic and steadfast, so I wrote something like this :

normal(S, R):- var(S), !, S = R.
normal(true(S), R):- !, normal(S, R).
normal(!(S), R):- !, normal(false(S), R).
normal(P => Q, R):- !, normal(false(P and false(Q)), R).
normal(A or B, R):- !, normal(false(false(A) and false(B)), R).
normal(false(S), R):- !, normal(S, NS), normal_false(NS, R).
normal(A and B, R):- !, normal(A, NA), normal(B, NB), normal_and(NA, NB, R).
normal(S, S):- !.

normal_false(S, R):- var(S), !, S = false(R).
normal_false(false(S), S):- !.
normal_false(true, false):- !.
normal_false(false, true):- !.
normal_false(S, false(S)):- !.

normal_and(A, B, R):- var(A), var(B), !, R = A and B.
normal_and(A, true, A):- !.
normal_and(true, B, B):- !.
normal_and(_, false, false):- !.
normal_and(false, _, false):- !.
normal_and(A, B, A and B):- !.

I'm now wondering if this was the right way to do it. It currently seems to work, but I'm wondering if this might not fit the properties I'm expecting in some edge-cases, if there might be some performance problems with the way I wrote it, or if this is just bad coding style/practice in general.

false
  • 10,264
  • 13
  • 101
  • 209
Xenos
  • 155
  • 5
  • If you're using cuts for control flow, then yes, one is too many. If it's for efficiency then no. https://en.wikipedia.org/wiki/Cut_(logic_programming) – Enigmativity May 21 '21 at 23:15
  • 2
    Thanks for your answer, so according to this wikipedia article, I should add \+ or != sub-goals to all those clauses to make sure that they would behave the same without the cuts and independently of the order of those clauses? This seems very tedious and even problematic for performance to me, considering I'd have to basically add a negative condition in every clause for every other clause of that predicate, or am I understanding this incorrectly? – Xenos May 21 '21 at 23:29
  • 1
    I think the point is that to use cuts for control flow you are thinking in terms of execution, whereas Prolog is best thought of declaratively. It's possible that you are missing the true power of Prolog if you use cuts in this way. – Enigmativity May 21 '21 at 23:35
  • I see, so I should try to find a way to redesign this procedure using as few cuts as possible, but not necessarily by bloating it with conditions – Xenos May 21 '21 at 23:38
  • 2
    Yes, most of the time you can redesign to avoid(most) cuts. For e.g., in the `normal/2` predicate above, you need to avoid the last clause that matches every formula. If you covered every binary and unary formula, then you can skip that clause and avoid all cuts except the first one. – rajashekar May 22 '21 at 05:25
  • Thank you, that's a really helpful answer! I already started redesigning the procedure and have removed all the cuts, but I still have that last clause that matches everything. I'm still not sure of the extent of the syntax I want to use so I'm gonna keep it for now, but I'll keep in mind to replace it by atomic rules as soon as I know the whole scope of the syntax. – Xenos May 22 '21 at 05:41
  • 1
    @Xenos: You cannot remove all the cuts **and** keep the last clause. – false May 23 '21 at 14:26
  • 1
    @false Yeah, I noticed it breaks determinism, so I replaced the last clause with a term thay only matches atoms now – Xenos May 23 '21 at 14:33

1 Answers1

4

Several questions arise when using the cut.

What kind of Prolog program are you writing?

There are several possibilities here. Starting from pure programs that can be understood declaratively by considering the set of solutions only. In such programs a lot of desirable properties hold, but yours is clearly not one of those. After all, the goal normal_false(false, true) succeeds, yet its generalization normal_false(V, true) fails (as intended). So normal_false/2 is not a pure relation.

Your program treats Prolog variables as objects. It is rather a meta-logical program which is not surprising as you want to implement a prover by reusing Prolog's infrastructure. Here, still many of the nice properties hold, you may for example still use a to identify reasons for non-termination. However, using var/1 is particularly error-prone since Prolog cannot enumerate all possible solutions and as a consequence, it is very easy to forget a case or two. In your example (after adding the missing operator declarations):

?- normal(X and true, N).
   X = N.                   % expected       
?- normal(true and X, N).
   X = true, N = true.      % unexpected

What does the cut cut away?

In normal/2 in the first clause normal(S, R):- var(S), !, ... the cut ensures that all subsequent clauses consider only the case nonvar(S).

Suspicious is the last clause normal(S, S):- !. which cuts if the arguments unify. At first sight that does not make a difference as it is the last clause. But you would get different results in case of, say freeze(S, ( T = 1 ; T = 2 ) ). But otherwise, normal/2 seems quite OK.

Much less convincing are the cuts in normal_and/2. In addition to above anomalies, this definition is not steadfast. After all, the outcome is influenced by its last argument:

?- normal(true and true, N).
   N = true.      % expected
?- normal(true and true, true and true).
   true.          % unexpected  

Here the cut came too late.

false
  • 10,264
  • 13
  • 101
  • 209
  • I didn't consider the `normal(true and true, true and true)` case, thanks for pointing that out. I now use == comparisons, as well as subsumes_term and copy_term to check/apply normalization rules, which I believe makes it sematically closer to the behaviour I want and less error prone, and allowed me to remove the clause that tests for `var`, as now variables can be used in the statements themselves and be bound as part of the proof. However, I think the example you pointed out would still work incorrectly in my current iteration, so I will look into it. – Xenos May 23 '21 at 14:43
  • @Xenos: Maybe look at [leantap](https://formal.iti.kit.edu/beckert/leantap/). – false May 23 '21 at 15:03
  • I'll look into it when I have the time, seems like I could learn a few things from it – Xenos May 23 '21 at 15:06