Search code examples
prologfactorialprolog-assert

Factorial calculation without using recursion


I'm trying to implement a solution for the factorial calculation (n!) without using recursion, only using the retroaction of prolog. For example:

factorial(0, 1).
factorial(1, 1).
factorial(2, 2).

retroaction(X, Y) :-
factorial(K, Y),
not(K = X),
fail.

retroaction(K, Y) :- factorial(K, Y).   

With fixed facts factorial, if I call the predicate retroaction to discover the factorial of 2, for example, I'll receive the correct answer:

?- retroaction(2, X).
X = 2.

The solution that I'm trying to implement is:

factorial_ret(Number, _) :-
    retractall(factorial(_, _)),
    assertz(factorial(0,1)),
    factorial(X, Y),
    not(X = Number),
    NewNumber is X + 1,
    Factorial is Y * NewNumber,
    retractall(factorial(_, _)),
    assertz(factorial(NewNumber, Factorial)),
    fail.

factorial_ret(Number, Y) :- 
    factorial(Number, Y).

If I try get the factorial of a number greater than 1, doesn't works. Someone have any idea of the why?


Solution

  • As @lurker pointed out, you're confusing the 'storage predicate' with the 'definition predicate'. You need the factorial/2 definition, so it makes little sense to retractall(factorial(_,_)).

    Below, I use retract(mem(N, F)) to retrieve the temporaries values and prepare the DB for an eventual update. There should be always only an instance of mem/2.

    % replace recursion with a failure driven loop
    :- dynamic mem/2.
    factorial(X, Y) :-
        assert(mem(0, 1)),
        repeat,
        retract(mem(N, F)),
        (  X == N % done ?
        -> Y = F, % yes, succeed
           !      % but be sure to avoid 'external' backtracking...
        ;  N1 is N + 1,
           F1 is N1 * F,
           assert(mem(N1, F1)),
           fail   % loop: 'goto repeat'
        ).
    

    Note that all builtins in the branch between repeat,...,fail are non backtrackable (well, except retract/1).

    Please note also that such code will be much slower of a recursive definition (and will not work in multithreaded SWI-Prolog).

    I think that assert/retract were made available early to Prolog programmers as required to handle 'knowledge bases', not to serve as global variables. Several Prologs have specialized library predicate for global variables, since there are legitimate uses of them: for instance, see memoization.

    PS: 'retroactive' is a term used in linear systems stability theory, I've never seen it used about programming

    edit Could be instructive to see how the bug reported by @repeat can be solved: just swap the unification and the cut. Well, we also need an test that X is a positive integer. Sincerely, I think this is actually irrelevant for the question at hand.

    ...
        (  X == N % done ?
        -> !,     % yes, be sure to avoid further backtracking
           Y = F  % unify with the computed factorial
        ;  N1 is N + 1,
    ...