Question

I am taking a class on programming paradigms. Currently we are learning Prolog, and I am stuck in a different paradigm. One of the functions I'm trying to convert from imperative is a relatively simple function.

foo(A,B,C)
    if(A > B) C is 1
    if(A < B) C is -1
    if(A = B) C is 0

I could do this in Prolog pretty easily.

foo(A,B,C) :- sub(A-B,C).

sub(E, C) :- E = 0, C is 0.
sub(E, C) :- E > 0, C is 1.
sub(E, C) :- E < 0, C is -1.

The problem is, I can only use one "is" in the entire predicate (can't define a smaller predicate that calls Is and calls that instead or anything), and I cannot use Prolog's if/else constructs. I cannot figure out how to think about this problem in a declarative manner.

I thought maybe I could do something like C is (A-B)/abs(A-B), but that breaks on A=B, and would require 2 "is" statements. I am just stuck.

Was it helpful?

Solution

In order to program declaratively, we have to think about what we are trying to declare and then we can describe it. To this end, I would try to write the predicate so that it expresses why the value of C alternates between three possible states depending on the relative sizes of A and B. Here's a predicate with the same form that you describe with foo:

comparison(A, B, less)     :-  A < B.
comparison(A, B, equal)    :-  A =:= B. %% =:=/2 is "True if expression A evaluates to a number equal to B".
comparison(A, B, greater)  :-  A > B.

You don't need the front-end predicate foo/3. You might read the lines thus: "The comparison of A and B is <Value> if A is <relation> with regard to B." This program consists of three rules that describe the rule comparison/3. The '3' indicates that this rule defines a relation between the 3 elements given to it as arguments.

You can query this program:

?- comparison(1,3, X).
X = less ;
false.

OTHER TIPS

Maybe I've misunderstood, but why can't you do this:

foo(A, B, 1) :- A > B.
foo(A, B, -1) :- A < B.
foo(A, B, 0) :- A =:= B.

i.e just embed the value of 'C' directly as an output parameter.

?- foo(1,3,C).
C = -1 .

?- foo(2,2,C).
C = 0.

If you already have the value of C that needs no computation, there's no need to declare it as a variable ?

Can you have multiple rules?

sub(0,0).
sub(E,C) :- C is E/abs(E).

This will always match the first rule if you pass in 0 for A-B and C will then unify with 0. You technically only need one is and you are using a base case to define this. To make it rule-order independent, you can also through in the clause E \= 0, but I don't know if this would validate the one is, since \= (does not unify) is essentially the same thing (no pun intended) but with any ground term not just int.

Arithmetic in Prolog is handled - for efficiency reasons - in 'traditional', imperative way. But when we can restrict the domain of computation, there are some libraries we can use to raise the level of the language, and regain some the lost declarativeness...

For instance, in SWI-Prolog, library(clpfd) - limited to integer domain - would allow to write

:- [library(clpfd)].

foo(A,B,C) :-
    A #> B #<==> C #= 1,
    A #< B #<==> C #= -1,
    A #= B #<==> C #= 0.

that is a rather general relation among A,B,C. For instance

1 ?- foo(1,2,C).
C = -1.

2 ?- foo(2,1,C).
C = 1.

3 ?- foo(2,X,1).
X in inf..1,
X+1#=_G219,
_G219 in inf..2.

4 ?- foo(X,Y,1).
X#\=Y,
X+1#=_G519,
X#>=_G531,
Y#=<_G519+ -1,
Y+1#=_G531.

queries 3 and 4 show the generality offered by the library (I read inf as -infinity), displaying the residual constraints deduced from instantiation pattern - and our own equations, of course.

Anyway, I would translate your code in plain Prolog in this way

foo(A,B,C) :-
    A > B, C is 1 ;
    A < B, C is -1 ;
    A =:= B, C is 0.

that requires A,B instantiated to numeric expressions.

Note I left unchanged is/2. That way, if C is instantiated to an arithmetic expression, it will work as expected...

Why wouldn't you just write the obvious one-liner?

foo(A,B,C) :- C is truncate(sign(A-B)).

They're both part of ISO prolog:

  • sign/1
    Evaluate[s] to -1 if Expr < 0, 1 if Expr > 0 and 0 if Expr = 0. If Expr evaluates to a float, the return value is a float (e.g., -1.0, 0.0 or 1.0). In particular, note that sign(-0.0) evaluates to 0.0.

  • truncate/1
    Truncate[s] Expr to an integer. If Expr >= 0 this is the same as floor(Expr). For Expr < 0 this is the same as ceil(Expr). That is, truncate/1 rounds towards zero.

This requires that A and B both be bound to numeric values (floating point or integer, or a mix).

If you want to generalize this across any type of prolog term, then something like this:

foo(A,B,  1 ) :- A @> B .
foo(A,B,  0 ) :- A == B .
foo(A,B, -1 ) :- A @< B .

You'll find that that using these operators can sometimes yield...odd results (for instance, you'll discover that -1.0 @< 1 is true. If you want to enforce more "normal" semantics when you've got numbers, then you'd want to highjack the case when both terms are numeric :

foo(A,B,  C ) :- number(A), number(B), ! , C is truncate(sign(A-B)) .
foo(A,B,  1 ) :- A @> B .
foo(A,B,  0 ) :- A == B .
foo(A,B, -1 ) :- A @< B .
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top