7
votes

I am currently trying to learn some basic prolog. As I learn I want to stay away from if else statements to really understand the language. I am having trouble doing this though. I have a simple function that looks like this:

if a > b then 1
else if
   a == b then c
else
    -1;;

This is just very simple logic that I want to convert into prolog.

So here where I get very confused. I want to first check if a > b and if so output 1. Would I simply just do:

sample(A,B,C,O):-
   A > B, 1,
   A < B, -1,
   0.

This is what I came up with. o being the output but I do not understand how to make the 1 the output. Any thoughts to help me better understand this?

After going at it some more I came up with this but it does not seem to be correct:

Greaterthan(A,B,1.0).
Lessthan(A,B,-1.0).
Equal(A,B,C).

Sample(A,B,C,What):-
    Greaterthan(A,B,1.0),
    Lessthan(A,B,-1.0),
    Equal(A,B,C).

Am I headed down the correct track?

4
What kind of thing might C be?Shon

4 Answers

6
votes

If you really want to try to understand the language, I recommend using CapelliC's first suggestion:

sample(A, B, _,  1) :- A > B.
sample(A, B, C,  C) :- A == B.
sample(A, B, _, -1) :- A < B.

I disagree with CappeliC that you should use the if/then/else syntax, because that way (in my experience) it's easy to fall into the trap of translating the different constructs, ending up doing procedural programming in Prolog, without fully grokking the language itself.

6
votes

TL;DR: Don't.

You are trying to translate constructs you know from other programming languages to Prolog. With the assumption that learning Prolog means essentially mapping one construct after the other into Prolog. After all, if all constructs have been mapped, you will be able to encode any program into Prolog.

However, by doing that you are missing the essence of Prolog altogether.

Prolog consists of a pure, monotonic core and some procedural adornments. If you want to understand what distinguishes Prolog so much from other programming languages you really should study its core first. And that means, you should ignore those other parts. You have only so much attention span, and if you waste your time with going through all of these non-monotonic, even procedural constructs, chances are that you will miss its essence.

So, why is a general if-then-else (as it has been proposed by several answers) such a problematic construct? There are several reasons:

  1. In the general case, it breaks monotonicity. In pure monotonic Prolog programs, adding a new fact will increase the set of true statements you can derive from it. So everything that was true before adding the fact, will be true thereafter. It is this property which permits one to reason very effectively over programs. However, note that monotonicity means that you cannot model every situation you might want to model. Think of a predicate childless/1 that should succeed if a person does not have a child. And let's assume that childless(john). is true. Now, if you add a new fact about john being the parent of some child, it will no longer hold that childless(john) is true. So there are situations that inherently demand some non-monotonic constructs. But there are many situations that can be modeled in the monotonic part. Stick to those first.

  2. if-then-else easily leads to hard-to-read nesting. Just look at your if-then-else-program and try to answer "When will the result be -1"? The answer is: "If neither a > b is true nor a == b is true". Lengthy, isn't it? So the people who will maintain, revise and debug your program will have to "pay".

From your example it is not clear what arguments you are considering, should you be happy with integers, consider to use library(clpfd) as it is available in SICStus, SWI, YAP:

sample(A,B,_,1) :- A #> B.
sample(A,B,C,C) :- A #= B.
sample(A,B,_,-1) :- A #< B.

This definition is now so general, you might even ask

When will -1 be returned?

?- sample(A,B,C,-1).
  A = B,
  C = -1,
  B in inf..sup
;
  A#=<B+ -1.

So there are two possibilities.

4
votes

Here are some addenda to CapelliC's helpful answer:

When starting out, it is sometimes easy to mistakenly conceive of Prolog predicates functionally. They are either not functions at all, or they are n-ary functions which only ever yield true or false as outputs. However, I often find it helpful to forget about functions and just think of predicates relationally. When we define a predicate p/n, we're describing a relation between n elements, and we've named the relation p.

In your case, it sounds like we're defining conditions on an ordered triplet, <A, B, C>, where the value of C depends upon the relation between A and B. There are three relevant relationships between A and B (here, since we are dealing with a simple case, these three are exhaustive for the kind of relationship in question), and we can simply describe what value C should have in the three cases.

sample(A, B, 1.0)  :-
    A > B.
sample(A, B, -1.0) :-
    A < B.
sample(A, B, some_value) :-
    A =:= B.

Notice that I have used the arithmetical operator =:=/2. This is more specific than ==/2, and it lets us compare mathematical expressions for numerical equality. ==/2 checks for equivalence of terms: a == a, 2 == 2, 5+7 == 5+7 are all true, because equivalent terms stand on the left and right of the operator. But 5+7 == 7+5, 5+7 == 12, A == a are all false, since it are the terms themselves which are being compared and, in the first case the values are reversed, in the second we're comparing +(5,7) with an integer and in the third we're comparing a free variable with an atom. The following, however, are true: 2 =:= 2, 5 + 7 =:= 12, 2 + 2 =:= 4 + 0. This will let us unify A and B with evaluable mathematical expressions, rather than just integers or floats. We can then pose queries such as

?- sample(2^3, 2+2+2, X).
X = 1.0 
?- sample(2*3, 2+2+2, X).
X = some_value.

CapelliC points out that when we write multiple clauses for a predicate, we are expressing a disjunction. He is also careful to note that this particular example works as a plain disjunction only because the alternatives are by nature mutually exclusive. He shows how to get the same exclusivity entailed by the structure of your first "if ... then ... else if ... else ..." by intervening in the resolution procedure with cuts. In fact, if you consult the swi-prolog docs for the conditional ->/2, you'll see the semantics of ->/2 explained with cuts, !, and disjunctions, ;.

I come down midway between CapelliC and SQB in prescribing use of the control predicates. I think you are wise to stick with defining such things with separate clauses while you are still learning the basics of the syntax. However, ->/2 is just another predicate with some syntax sugar, so you oughtn't be afraid of it. Once you start thinking relationally instead of functionally or imperatively, you might find that ->/2 is a very nice tool for giving concise expression to patterns of relation. I would format my clause using the control predicates thus:

sample(A, B, Out) :-
    ( A > B   -> Out = 1.0
    ; A =:= B -> Out = some_value
    ; Out = -1.0
    ).
2
votes

Your code has both syntactic and semantic issues.

Predicates starts lower case, and the comma represent a conjunction. That is, you could read your clause as

sample(A,B,C,What) if
    greaterthan(A,B,1.0) and lessthan(A,B,-1.0) and equal(A,B,C).

then note that the What argument is useless, since it doesn't get a value - it's called a singleton.

A possible way of writing disjunction (i.e. OR)

sample(A,B,_,1) :- A > B.
sample(A,B,C,C) :- A == B.
sample(A,B,_,-1) :- A < B.

Note the test A < B to guard the assignment of value -1. That's necessary because Prolog will execute all clause if required. The basic construct to force Prolog to avoid some computation we know should not be done it's the cut:

sample(A,B,_,1) :- A > B, !.
sample(A,B,C,C) :- A == B, !.
sample(A,B,_,-1).

Anyway, I think you should use the if/then/else syntax, even while learning.

sample(A,B,C,W) :- A > B -> W = 1 ; A == B -> W = C ; W = -1.