On TIO, your code into code and your query goes into the input
For whatever reason, the author of the power of prolog does not mention explicitely that you have to include the CLP(FD) library to make integer arithmetic work
@Pavel X is 2+2 is right but this is not the declarative way to do integer arithmetic in Prolog. You should include the CLP(FD) library by including :- use_module(library(clpfd)). (or by querying use_module(library(clpfd)).), and then you can use constraint arithmetic: X #= 2+2. In that case it doesn't change anything, but try X * X #= 4 and X * X is 4 to see the major difference
@Pavel If you have SWI-Prolog on your computer, you can query make. after changing your code file to update the rules base without having to reconsult.
the only benefits of is is that it's faster, but for "simple" operations this is completely negligible
and anyway Prolog isn't the language you need if speed is crucial
@dzaima In Brachylog, there is no way to do is-like computations with integers. They all use CLP(FD) (which I think is the only declarative language where this is the case)
would it be right to say that the main form of calculation in vanilla prolog is through reduction to a base case? I've worked through reversing a list in prolog and addition using peano, and it all seems to use an accumulator of some sort to keep track of state. would this be a correct observation?
The main form of calculation is recursivity, so in a sense yes
accumulators is a very common computation pattern in Prolog
and in general, you indeed pass the state through different predicates using arguments, instead of manipulating global variables
@ConorO'Brien A good exercise imo is to try to implement factorial (using CLP(FD) for int operations). There are two recursive ways of doing it, and one is much better than the other
> Write a predicate `f/2` (i.e. a predicate with name `f` and 2 arguments) which states that its second argument is the factorial of its first argument.
To do integer operations, add `:- use_module(library(clpfd)).` at the beginning of your code base. Some operations you might need are (with dummy variables): `A #> B`, `A #= B * C`, `A #= B - C`.
So really your code was right with what you knew. In reality your base case should be factorial(0,1)., but otherwise your solution is what I expected :)
@Pavel It returns false because it think there might be another answer. Indeed, once it reaches the base case, it didn't check if reusing the second rule instead of the base case would yield another solution. So when you press ;, it tries it, and finds that there is in fact no other solution. So this works as expected!
@dzaima if you see it like this I guess… :p
Now, for both of you, observe this: if you move your R #= R0 * I / X #= F*X before the recursive call, then it still works as expected.
(which obviously wouldn't be the case with imperative languages)
@dzaima You can't get it with is because it indeed expects that the value of F is known in order to computer X, which it wouldn't know before recursively calling the predicate
with CLP(FD), it only constrains the variable called F such that Y = F*X, and will retroactively compute the value of Y once it is unambiguously possible to do so
The reason for this, is that your recursive call is indeed before your R #= R0 * I constraint. Because of this, it means that, when R is known but I isn't (instead of the opposite as before), it will call factorial(I0,R0) without knowing anything about either I0 nor R0
Therefore, it will always find a way to satisfy the first constraints and call itself recursively infinitely
Lesson is: always apply the needed constraints before making recursive calls.
The second lesson, after fixing this, is that while you only wrote a "compute the factorial" predicate, you actually end up also with a "what number has this factorial" predicate, for free!
In fact, you also end up with a factorial numbers generator:
?- factorial(X,Y).
X = 0,
Y = 1 ;
X = Y, Y = 1 ;
X = Y, Y = 2 ;
X = 3,
Y = 6 ;
X = 4,
Y = 24 ;
X = 5,
Y = 120 ;
…
All of those behaviors for the price of a few lines of codes
As Pavel said, if you don't constrain that A is greater than 0, then it will do badly for negative numbers
The better one is the second exercise:
> Observe that factorial(40,Y). returns Y = 815915283247897734345611269596115894272000000000 immediately, while factorial(X,815915283247897734345611269596115894272000000000). returns X = 40 really slowly. (and for the factorial of 100, it's obviously way worse). Implement a new version of the factorial predicate which works quickly in both cases.