views:

101

answers:

3

While reading SICP I came across logic programming chapter 4.4. Then I started looking into the Prolog programming language and tried to understand some simple assignments in Prolog. I found that Prolog seems to have troubles with numerical calculations.

Here is the computation of a factorial in standard Prolog:

f(0, 1).
f(A, B) :- A > 0, C is A-1, f(C, D), B is A*D.

The issues I find is that I need to introduce two auxiliary variables (C and D), a new syntax (is) and that the problem is non-reversible (i.e., f(5,X) works as expected, but f(X,120) does not).

Naively, I expect that at the very least C is A-1, f(C, D) above may be replaced by f(A-1,D), but even that does not work.

My question is: Why do I need to do this extra "stuff" in numerical calculations but not in other queries?

I do understand (and SICP is quite clear about it) that in general information on "what to do" is insufficient to answer the question of "how to do it". So the declarative knowledge in (at least some) math problems is insufficient to actually solve these problems. But that begs the next question: How does this extra "stuff" in Prolog help me to restrict the formulation to just those problems where "what to do" is sufficient to answer "how to do it"?

+3  A: 

Forget about variables and think that A and B - is just a name for value which can be placed into that clause (X :- Y). to make it reachable. Think about X = (2 + (3 * 4)) in the way of data structures which represent mathematical expression. If you will ask prolog to reach goal f(A-1, B) it will try to find such atom f(A-1,B). or a rule (f(A-1,B) :- Z), Z. which will be unified to "success".
is/2 tries to unify first argument with result of interpreting second argument as an expression. Consider eval/2 as variant of is/2:

eval(0, 1-1). eval(0, 2-2). eval(1,2-1).
eval(Y, X-0):- eval(Y, X).
eval(Y, A+B):- eval(ValA, A), eval(ValB, B), eval(Y, ValA + ValB).
eval(4, 2*2).
eval(0, 0*_). eval(0, _*0).
eval(Y, X*1):- eval(Y, X).
eval(Y, 1*X):- eval(Y, X).
eval(Y, A*B):- eval(ValA, A), eval(ValB, B), eval(Y, ValA * ValB).

The reason why f(X,120) doesn't work is simple >/2 works only when its arguments is bound (i.e. you can't compare something not yet defined like X with anything else). To fix that you have to split that rule into:

f(A,B) :- nonvar(A), A > 0, C is A-1, f(C, D), B is A*D.
f(A,B) :- nonvar(B), f_rev(A, B, 1, 1).

% f_rev/4 - only first argument is unbound.
f_rev(A, B, A, B). % solution 
f_rev(A, B, N, C):- C < B, NextN is (N+1), NextC is (C*NextN), f_rev(A, B, NextN, NextC).

Update: (fixed f_rev/4)
You may be interested in finite-domain solver. There was a question about using such things. By using #>/2 and #=/2 you can describe some formula and restrictions and then resolve them. But these predicates uses special abilities of some prolog systems which allows to associate name with some attributes which may help to narrow set of possible values by intersection of restriction. Some other systems (usually the same) allows you to reorder sequence of processing goals ("suspend").
Also member(X,[1,2,3,4,5,6,7]), f(X, 120) is probably doing the same thing what your "other queries" do.

If you are interested in logical languages in general you may also look at Curry language (there all non-pure functions is "suspended" until not-yed-defined value is unified).

ony
+1: Smart explanation
The chicken in the kitchen
To my understanding, your reverse function `f_rev` has an off-by-1 error. `NextC is (NextN*C)` instead of `NextC is (C*N)` seems to fix it.
user8472
Your explanation is plausible, but to me (with a background in functional and procedural programming) it is not intuitive how to find it starting from scratch. It seems that I *still* need to find the recipe of "how" to solve the problem on my own and then just recast the algorithm in declarative form. In *some* cases, however, Prolog **can** form the "how" recipe from the "what"; and the factorial case does not seem to be one of those! Is there any way to figure out whether or not I can formulate a problem in a declarative language without having to find an algorithm for the solution first?
user8472
@user8472: Difference between those "_some_ cases" and factorial is that "_some_ cases" use pure atoms and rules where you are doing only unification and referring to other pure rules/atoms. You give all the information, but when you do `>/2` you are referring to something that have no declarative description, only reference to native functions of comparing two native integers. Same for `is/2` it uses native instructions to calculate. If you'll use fully-defined `eval/2` you'll get it work in reverse way `eval(0, X-1).` will give you `X=1`. That's problem of many declarative languages, I think
ony
@ony, Ah, I think I get it now! If I had a complete database of facts about natural numbers in the form of `eval/2` (as you have sketched) then I could indeed solve the problem without having to invent the algorithm beforehand. Of course, such a complete database would be gigantic already for numbers up to 120, and simply trying out all possibilities (in particular if you don't know that you only need to go up to 120) is impractical. Thanks, that was really helpful!
user8472
A: 

SWI-Prolog's CLP(FD) manual contains the following definition of n_factorial/2:

:- use_module(library(clpfd)).

n_factorial(0, 1).
n_factorial(N, F) :- N #> 0, N1 #= N - 1, F #= N * F1, n_factorial(N1, F1).

and the following example queries:

?- n_factorial(47, F).
F = 258623241511168180642964355153611979969197632389120000000000 ;
false.

?- n_factorial(N, 1).
N = 0 ;
N = 1 ;
false.

?- n_factorial(N, 3).
false.
mat
+1  A: 

There are some things which you must remember when looking at Prolog:

  • There is no implicit return value when you call a predicate. If you want to get a value out of a call you need to add extra arguments which can be used to "return" values, the second argument in your f/2 predicate. While being more verbose it does have the benefit of being easy to return many values.

  • This means that automatically "evaluating" arguments in a call is really quite meaningless as there is no value to return and it is not done. So there are no nested calls, in this respect Prolog is flat. So when you call f(A-1, D) the first argument to f/2 is the structure A-1, or really -(A, 1) as - is an infix operator. So if you want to get the value from a call to foo into a call to bar you have to explicitly use a variable to do it like:

    foo(..., X), bar(X, ...),

  • So you need a special predicate which forces arithmetic evaluation, is/2. It's second argument is a structure representing an arithmetic expression which it interprets, evaluates and unifies the result with its first argument, which can be either a variable or numerical value.

  • While in principle you can run things backwards with most things you can't. Usually it is only simple predicates working on structures for which it is possible, though there are some very useful cases where it is possible. is/2 doesn't work backwards, it would be exceptional if it did.

This is why you need the extra variables C and D and can't replace C is A-1, f(C, D) by f(A-1,D).

(Yes I know you don't make calls in Prolog, but evaluate goals, but we were starting from a functional viewpoint here)

rvirding