Guard clauses in prolog?
Asked Answered
D

4

10

Do they exist? How are they implemented?

The coroutining predicates of SWI-Prolog (freeze, when, dif etc.) have the functionality of guards. How do they fit in the preferred Prolog programming style?

I am very new to logic programming (with Prolog and altogether) and somewhat confused by the fact that it is not purely declarative, and requires procedural considerations even in very simple cases (see this question about using \== or dif). Am I missing something important?

Dangle answered 7/12, 2012 at 9:8 Comment(2)
As noted below by editor "false" below, the term "guard" is the wrong one here, as "guards" are used to signify the bog-standard precondition, e.g. in an 'if-then' expression. The correct one is "guarded suspension". I hope. I have left the original formulation as "guard" in the question though.Mensch
With some math you can do safely freeze/2, see here: https://mcmap.net/q/754177/-freeze-2-goals-blocking-on-variables-that-have-become-unreachableOvolo
A
6

First a terminological issue: Neither freeze/2 nor when/2 nor dif/2 are called guards under any circumstance. Guards appear in such extensions as CHR, or related languages as GHC (link in Japanese) or other Concurrent logic programming languages ; you even (under certain restrictions) might consider clauses of the form

Head :- Guard, !, ...

as clauses containing a guard and the cut would be in this case rather called a commit. But none applies to above primitives. Guards are rather inspired by Dijkstra's Guarded Command Language of 1975.

freeze(X, Goal) (originally called geler) is the same as when(nonvar(X), Goal) and they both are declaratively equivalent to Goal. There is no direct relation to the functionality of guards. However, when used together with if-then-else you might implement such a guard. But that is pretty different.

freeze/2 and similar constructs were for some time considered as a general way to improve Prolog's execution mechanism. However, they turned out to be very brittle to use. Often, they were too conservative thus delaying goals unnecessarily. That is, almost every interesting query produced a "floundering" answer as the query below. Also, the borderline between terminating and non-terminating programs is now much more complex. For pure monotonic Prolog programs that terminate, adding some terminating goal into the program will preserve termination of the entire program. However, with freeze/2 this is no longer the case. Then from a conceptual viewpoint, freeze/2 was not very well supported by the toplevels of systems: Only a few systems showed the delayed goals in a comprehensive manner (e.g. SICStus) which is crucial to understand the difference between success/answers and solution. With delayed goals Prolog may now produce an answer that has no solution as this one:

?- freeze(X, X = 1), freeze(X, X = 2).
   freeze(X, X=1), freeze(X, X=2).

Another difficulty with freeze/2 was that termination conditions are much more difficult to determine. So, while freeze was supposed to solve all the problems with termination, it often created new problems.

And there are also more technical difficulties related to freeze/2 in particular w.r.t tabling and other techniques to prevent loops. Consider a goal freeze(X, Y = 1) clearly, Y is now 1 even if it is not yet bound, it still awaits X to be bound first. Now, an implementation might consider tabling for a goal g(Y). g(Y) will now have either no solution or exactly one solution Y = 1. This result would now be stored as the only solution for g/1 since the freeze-goal was not directly visible to the goal.

It is for such reasons that freeze/2 is considered the goto of constraint logic programming.

Another issue is dif/2 which today is considered a constraint. In contrast to freeze/2 and the other coroutining primitives, constraints are much better able to manage consistency and also maintain much better termination properties. This is primarily due to the fact that constraints introduce a well defined language were concrete properties can be proven and specific algorithms have been developed and do not permit general goals. However, even for them it is possible to obtain answers that are not solutions. More about answer and success in CLP.

Acadian answered 8/12, 2012 at 20:39 Comment(5)
More terminology confusion, I fear. Outside of the Prolog universe, "guards" are Guards: Preconditions as used by Dijkstra, a boolen expression that evaluates to true or false, and is used to specify control flow in a declarative manner. The 'guards' of GHC and GDC and Parlog are parts of clause bodies that suspend execution of the body goals until a condition is met, similar to production system rule heads. Sounds very like 'freeze/2' (coroutines, right?), less like Dijkstra.Mensch
@DavidTonhofer: Key is that they, quite similar to Dijkstren's guards, are only if-then's. There is no else. If guards overlap then it is random/chronological backtracking that makes the choice. So you cannot express negation as failure with guards.Acadian
More on GHC/GDC, in particular, history in Chapter 3 ("Metamorphosis") of "Agent-Oriented Programming - From Prolog to Guarded Definite Clauses", a Lecture Note in Computer Science Springer Book that can also be obtained by ... other means. It has been written rather quickly though and contains a number of technical errors and confusing formulations. Still worth it.Mensch
Aren't they more like "as soon as ... then"? Control flow comes from the big bag of available threads in the sky (this is unlike Dijkstra's guards, which have single thread flow). Wait, what happens when on backtracks over the freeze? One can never get rid of it as the condition it is waiting on may still become true later. Or am I misunderstanding semantics here? I need a paper reference.Mensch
if, when, ... I'd say studying this direction will not help you a lot. Concurrent Prolog and the like, are close to Prolog but not quite. The only current language with some inspiration of this in use is CHR.Acadian
H
4

freeze/2 and when/2 are like a "goto" of logic programming. They are not pure, commutative, etc.

dif/2, on the other hand, is completely pure and declarative, monotonic, commutative etc. dif/2 is a declarative predicate, it holds if its arguments are different. As to the "preferred Prolog programming style": State what holds. If you want to express the constraint that two general terms are different, dif/2 is exactly what states this.

Procedural considerations are typically most needed when you do not program in the pure declarative subset of Prolog, but use impure predicates that are not commutative etc. In modern Prolog systems, there is little reason to ever leave the pure and declarative subset.

Headley answered 7/12, 2012 at 9:23 Comment(6)
What do you mean here by "commutative"? Can you give an example?Acadian
I was thinking of the general case: since freeze/2 and when/2 impose no restrictions on their second argument, you can for example even use them for output, where the order of goals matters.Headley
freeze/2 is the goto of constraint logic programming ; but clearly not of logic programming.Acadian
Yes, of constraint logic programming. Clearly we are not yet fragmented enough.Headley
@Headley "In modern Prolog systems, there is little reason to ever leave the pure and declarative subset" A peremptory statement, my good fellow. I would affirm that our interpreters are rarely bottom-up (Starlog maybe? Datalog for sure) and we demand side effects lest we descend into solipsism!Mensch
Always aim for purity. For example, to read files, use pure input with Ulrich Neumerkel's library(pio). To print things, use the toplevel's built-in capability to display solutions. Focus an a clear declarative description using constraints, and you obtain more general programs that can be used in more directions.Headley
O
1

There is a paper by Evan Tick explaining CC:

Informally, a procedure invocation commits to a clause by matching the head arguments (passive unification) and satisfying the guard goals. When a goal can commit to more than one clause in a procedure, it commits to one of them non- deterministically (the other candidates are thrown away). Structures appearing in the head and guard of a clause cause suspension of execution if the correspond- ing argument of the goal is not sufficiently instantiated. A suspended invocation may be resumed later when the variable associated with the suspended invocation becomes sufficiently instantiated.

THE DEEVOLUTION OF CONCURRENT LOGIC PROGRAMMING LANGUAGES
Evan Tick - 1995
https://core.ac.uk/download/pdf/82787481.pdf

So I guess with some when/2 magic, a commited choice code can be rewritten into ordinary Prolog. The approach would be as follows. For a set of commited choice rules of the same predicate:

H1 :- G1 | B1.
...
H2 :- Gn | Bn.

This can be rewritten into the following, where Hi' and Gi' need to implement passive unification. For example by using ISO corrigendum subsumes_term/2.

H1' :- G1', !, B1.
..
H1' :- Gn', !, Bn.
H :- term_variables(H, L), when_nonvar(L, H).

The above translation wont work for CHR, since CHR doesn't throw away candidates.

Ovolo answered 4/11, 2018 at 2:20 Comment(0)
A
0

The syntax in SWI is Head, Guard => Body. For example:

timeOfDay(H, TOD), H @> 12, H @< 19 => TOD = "afternoon".

Please note that this was only added in SWI Prolog 8.3.19, and was inspired by Picat.

Anemic answered 13/7 at 8:23 Comment(1)
This answer is missing the question. The question itself is missing the point. SSU is a built-in feature of SWI-Prolog but semantically does not add anything novel that cannot be implemented with pure Prolog rules.Ciera

© 2022 - 2024 — McMap. All rights reserved.