using arithmetic operations in Prolog - prolog

I have the following code:
position(0,0).
move(f):-
position(X,Y),
number(X),
number(Y),
Y is Y+1,
X is X+1.
but when i call move(f) it returns false. number(X) and number(Y) returns true but whem i add the other two lines the function doesn't work. what's the problem?

Elaborating on some of the comments your question has received, variables in Prolog stand for a possible instantiation of a single value, just like variables in mathematics and mathematical logic, and once they are instantiated within a context they must remain consistent. If we're dealing with a formula 0 = (a + b) - (a + b), we know that it can only express its intended sense if any value assigned to the first a is also assigned to the second. That is, we can substitute any value for a, but it must be the same value throughout. Prolog works with variables in this same way. If x = x + 1, then 2 = 3; but then math would be broken.
Addressing mat's caution against using dynamic predicates, here is a possible way of handling moves, but accomplished by passing around a list of previous moves. With this method, the most recent move will always be the first element of List in the compound term moves(List).
Supposing the current history of moves is as follows:
moves([position(0,0), position(0,1), position(1,1)]).
move/3 takes a direction, a complex term representing the previous moves, and tells us what the updated list of moves is.
move(Direction, moves([From|Ms]), moves([To,From|Ms])) :-
move_in_direction(Direction,From,To).
move_in_direction/3 takes a direction, and a position, and tells us what the next position in that direction is:
move_in_direction(left, position(X1,Y1), position(X2,Y1)) :- X2 is X1 - 1.
move_in_direction(right, position(X1,Y1), position(X2,Y1)) :- X2 is X1 + 1.
move_in_direction(up, position(X1,Y1), position(X1,Y2)) :- Y2 is Y1 + 1.
move_in_direction(down, position(X1,Y1), position(X1,Y2)) :- Y2 is Y1 - 1.
Notice that, using this method, you get a back-trackable history of moves for free. I'd imagine you could use this in interesting ways -- e.g. having the player explore possible series of moves until a certain condition is met, at which point it commits or backtracks. I'd be interested to know what kind of solution you end up going with.

Related

Prolog: Chaining multiple rules

So I am an absolute beginner in Prolog. I am learning recursion at the moment and this is what I got until now on my task.
dance(start).
dance(forward(T)) :- dance(T).
dance(backward(T)) :- dance(T).
count(start,0,0).
count(forward(X),succ(Y),Z) :- count(X,Y,Z).
count(backward(X),Y,succ(Z)) :- count(X,Y,Z).
greater(succ(0),0).
greater(succ(Y),succ(Z)):-greater(Y,Z).`
Summary of what this is supposed to do: There can be dances starting with the "start" and then either forward or backward addition per recursion. In count, I managed to be able to count the amount of forward and backward in a given sequence and save them in the "succ" notation and in greater I want to compare two of such "succ" numbers. And greater shall be true if the first argument is larger (consists of more "succs" than the second argument.
Now my task is to write a new rule "more(X)" which is true if the sequence X (build from dance) has more forward than backward in it. I think I have all the subtasks I need for it but now I am helpless with chaining them together because until now I only had 2 rules with the same amount of parameters but in this case, I got one with one, two, and three parameters. The solution should be something like this
more(X):-greater(Y,Z)
but how do I get my "succ" umbers from "count" to "greater" and the given sequence X to "count"? I do have to change some of the rules otherwise count is never called, right?
So it should be more like more(X):-greater(count(X,Y,Z)) ?
But like this, I would have to change the greater rules to be able to "get" this type of parameter.
Example query ?- more(backward(forward(start))).
false.
?- more(forward(start)).
true.
Your dance/1 and count/3 predicates seems correct.
If you want "greater" where greater(X, Y) means that X is greater than Y, you'd write:
greater(succ(_), 0).
greater(succ(X), succ(Y)) :- greater(X, Y).
Your solution checks if X is exactly one greater than Y.
Note that nothing is greater than 0.
If you implement more/1 in terms of count/3 and greater/2, you'd write:
more(X) :-
count(X, Forward, Backward),
greater(Forward, Backward).
So it should be more like more(X):-greater(count(X,Y,Z)) ?
No, you are writing that as if it is Python and greater(count(X,Y,Z)) will call greater(...) on the return from count. Prolog predicates are not function calls, and count(...) does not return anything in that way.
The result of the count is Y and Z. (These names you are using could be clearer). Do the count, and then after, use the Y and Z for something else.
count(X,Y,Z),
greater(Y,Z)
The comma being AND; "this code works if count of X is Y and Z AND Y is greater than Z".

Prolog lists returns false

I'm relatively new in Prolog.
I'm trying to multiply and add all elements of a list, but it only works for one element.
What is wrong?
%Basic
mult([],A,0).
mult([X|Xs],A,S) :-
mult(Xs,A,R),
S is (X * A),
S is S + R.
Thanks for any help.
The problem is with the line S is S + R.. In prolog, one variable cannot take on different values within the body of a clause. Here you expect the value of S to change, but this cannot work (except if R is 0, in which case S stays the same; hence why it already works for single element lists).
You need to use a different variable to store the intermediate result, and only use the output variable for the final result. E.g.:
mult([], _, 0).
mult([X|Xs], A, S):-
mult(Xs, A, R),
Tmp is (X * A),
S is Tmp + R.
I also took the liberty to fix the singleton variable warning you were getting. Although in this case it was harmless, you should never ignore them as they often point to flawed logic.

SWI Prolog if statements, how do they work? Generating a simple grid

I realize I've edited out the if statements out of the original code which doesn't help readability and question clarity. Just skip to the answers for the explanation on how they work with a small example program.
To learn about more complex programs using if statements in Prolog, I'm creating a simple platformer that generates some objects and places them in a grid. First I'm trying to generate a simple 'world' with the idea of trying out generating things in prolog. The plan is to create a grid of 50 lists with 10000 items, which really shouldn't be that complicated but I can't get the if statements to work as I get the impression that I'm fundamentally misunderstanding how they work vs how I think they work. What happens is the condition isn't met, the if statement isn't called but the whole predicate is recalled with empty variables and evaluations are not instantiated.
Create a simple accumulator which has an X and Y axis, and limits to
how far they go before failing the predicate.
If the number of Y rows has been reached, terminate
Create a new [id, point(X,Y), Image] to be later filled with something
If X = end of the row, X is 0, else create the next point
Code:
generate(WorldList) :- generate_world(WorldList,0,_,10000,0,_,50).
generate_world([H|T],X,_,XEnd,Y,_,YEnd) :-
%Y has been filled with 50 rows, end recursion
not(Y > YEnd),
%iterate X by 1, store in XNew
XNew is X + 1,
%create a new [id,point(X,Y), Image]
H = [XNew,point(_,_)],
%if X has reached 10k, add 1 to Y and create a new row
X = XEnd -> YNew is Y + 1,
generate_world(T,0,_,XEnd,YNew,_,YEnd);
%continue adding items to current row Y
generate_world(T,XNew,_,XEnd,Y,_,YEnd).
generate_world([],_,_,_,_,_,_).
Am I doing something blatantly wrong or how are you supposed to use prolog conditional statements and can they even be used like this at all?
The way I expect it to work is a term is evaluated, then do what is to the left of the following OR if it's true, or the right if it's false. That happens, but I don't understand why the entire predicate is called again as it also empties the variables being evaluated. My brain hurts.
What the docs say: http://www.swi-prolog.org/pldoc/man?predicate=-%3E/2
#damianodamiano identified the problem, if statements in prolog need to be surrounded by () tags. I'd still like a more detailed explanation of how they actually work in regards to choice points, backtracking and other Prolog specific things I might not even know about.
Your predicate stops as soon as you run it because in not(By > YEnd), By is not instantiated (note that By is also a singleton variable and each singleton variable is useless and can drive to errors). Here i post two implementation, the first without if statement (which personally prefer), the second with if statement (i've put 2 and 2 as bound for brevity...).
First implementation:
generateList(L):-
generateWL(L,0,2,0,2).
generateWL([],0,_,Y,Y). %you can add a ! here
generateWL(L,MaxX,MaxX,R,MaxR):- %you can add a ! here
R1 is R+1,
generateWL(L,0,MaxX,R1,MaxR).
generateWL([H|T],X,MaxX,R,MaxR):-
X < MaxX,
R < MaxR,
X1 is X+1,
H = [X1,point(X1,R)],
generateWL(T,X1,MaxX,R,MaxR).
?- generateList(WL).
WL = [[1, point(1, 0)], [2, point(2, 0)], [1, point(1, 1)], [2, point(2, 1)]]
false
If you want to prevent backtracking, just add the two cuts i've annotated.
Second implementation
generateList2(L):-
generateWLIf(L,0,2,0,2).
generateWLIf([H|T],X,MaxX,R,MaxR):-
( X < MaxX, R < MaxR ->
X1 is X+1,
H = [X1,point(X1,R)],
generateWL(T,X1,MaxX,R,MaxR)
; X = MaxX, R < MaxR ->
R1 is R+1,
generateWL([H|T],0,MaxX,R1,MaxR)
; R = MaxR -> T = []).
?- generateList2(WL).
WL = [[1, point(1, 0)], [2, point(2, 0)], [1, point(1, 1)], [2, point(2, 1)]]
(Continuing from the comments)
The way I expect [conditional statements] to work is a term is
evaluated, then do what is to the left of the following OR if it's
true, or the right if it's false. That happens, but I don't understand
why the entire predicate is called again as it also empties the
variables being evaluated.
You probably mean that it back-tracks, and the reason is that the comparison not(Y > YEnd) eventually fails, and there is no else-clause (and no if either).
Also, your base case makes no sense, as the list is output not input. And you want to compare against XNew not X.
generate(WorldList) :-
generate_world(WorldList,1,10000,1,50).
generate_world(T,X,XEnd,Y,YEnd) :-
( Y = YEnd ->
T = []
; T = [point(X,Y)|Rest], XNew is X + 1,
( XNew = XEnd -> YNew is Y + 1,
generate_world(Rest,1,XEnd,YNew,YEnd)
; generate_world(Rest,XNew,XEnd,Y,YEnd) ) ).
This would seem to work in the sense that it does what you describe, but it is not good design. Now you have to pass this enormous list around all the time, and updating one location means deconstructing the list.
Your problem:
I'm creating a simple platformer that generates some objects and
places them in a grid. First I'm trying to generate a simple 'world'
with the idea of trying out generating things in prolog. The plan is
to create a grid of 50 lists with 10000 items
is much better solved in Prolog by having a predicate location/3 (for example) where the parameters are the coordinates and the content.
location(1,1,something).
location(1,2,something).
location(1,3,somethingelse).
...
And this predicate is created dynamically, using assert/3.
This is based on my understanding of ISO-prolog and the other answers given, boiled down to the essence of how if then else works in Prolog.
The if predicate -> forces evaluation its the surrounding complex terms grouped by ( and ). The outer brackets identify the if-statement as ( if -> then ; else ), where if,then and else are each goals in the form of terms to be evaluated, which return yes or no, also grouped by ( and ). Whether then or else is called, separated by the OR operator ;, depends on the yes or no result from the evaluated term represented by if. The outer groupings are strictly necessary while the inner ones are optional, BUT it's good practice in my opinion to add them anyway, given that you can nest another if statement as a term surrounded by () in the result of the first, which likely produces unwanted result and makes the code much harder to read, and any non-grouped nested ; will identify the right side as the else.
Choice points are created where there are variables that can have multiple possible answers as a possible solution to the posed goal. This means within an if, if a term can be satisfied in multiple ways, Prolog will try to satisfy that goal as a separate goal and then use the result to determine the outcome of the surrounding term. If a goal fails, it behaves like normal code and doesn't try to satisfy the goals further right.
If a choice point is before the whole if statement section, the whole section will be checked again.
Example program to clarify the idea.
fact(a).
fact(f).
start :-
(
%The entire complex term is evaluated as yes
(fact(a), write('first if'), nl) ->
%triggers the first line
(write('first then'),nl) ;
(write('first else'),nl)
),
(
%The entire complex term is evaluated as no
(fact(B), write('second if'), B = b, nl) ->
(write('second then'),nl) ;
%triggers the second line
(write('second else'),nl)
).
And the output for ?- start.
first if
first then
second ifsecond ifsecond else

Fold over a partial list

This is a question provoked by an already deleted answer to this question. The issue could be summarized as follows:
Is it possible to fold over a list, with the tail of the list generated while folding?
Here is what I mean. Say I want to calculate the factorial (this is a silly example but it is just for demonstration), and decide to do it like this:
fac_a(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; numlist(2, N, [H|T]),
foldl(multiplication, T, H, F)
).
multiplication(X, Y, Z) :-
Z is Y * X.
Here, I need to generate the list that I give to foldl. However, I could do the same in constant memory (without generating the list and without using foldl):
fac_b(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; fac_b_1(2, N, 2, F)
).
fac_b_1(X, N, Acc, F) :-
( X < N
-> succ(X, X1),
Acc1 is X1 * Acc,
fac_b_1(X1, N, Acc1, F)
; Acc = F
).
The point here is that unlike the solution that uses foldl, this uses constant memory: no need for generating a list with all values!
Calculating a factorial is not the best example, but it is easier to follow for the stupidity that comes next.
Let's say that I am really afraid of loops (and recursion), and insist on calculating the factorial using a fold. I still would need a list, though. So here is what I might try:
fac_c(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; foldl(fac_foldl(N), [2|Back], 2-Back, F-[])
).
fac_foldl(N, X, Acc-Back, F-Rest) :-
( X < N
-> succ(X, X1),
F is Acc * X1,
Back = [X1|Rest]
; Acc = F,
Back = []
).
To my surprise, this works as intended. I can "seed" the fold with an initial value at the head of a partial list, and keep on adding the next element as I consume the current head. The definition of fac_foldl/4 is almost identical to the definition of fac_b_1/4 above: the only difference is that the state is maintained differently. My assumption here is that this should use constant memory: is that assumption wrong?
I know this is silly, but it could however be useful for folding over a list that cannot be known when the fold starts. In the original question we had to find a connected region, given a list of x-y coordinates. It is not enough to fold over the list of x-y coordinates once (you can however do it in two passes; note that there is at least one better way to do it, referenced in the same Wikipedia article, but this also uses multiple passes; altogether, the multiple-pass algorithms assume constant-time access to neighboring pixels!).
My own solution to the original "regions" question looks something like this:
set_region_rest([A|As], Region, Rest) :-
sort([A|As], [B|Bs]),
open_set_closed_rest([B], Bs, Region0, Rest),
sort(Region0, Region).
open_set_closed_rest([], Rest, [], Rest).
open_set_closed_rest([X-Y|As], Set, [X-Y|Closed0], Rest) :-
X0 is X-1, X1 is X + 1,
Y0 is Y-1, Y1 is Y + 1,
ord_intersection([X0-Y,X-Y0,X-Y1,X1-Y], Set, New, Set0),
append(New, As, Open),
open_set_closed_rest(Open, Set0, Closed0, Rest).
Using the same "technique" as above, we can twist this into a fold:
set_region_rest_foldl([A|As], Region, Rest) :-
sort([A|As], [B|Bs]),
foldl(region_foldl, [B|Back],
closed_rest(Region0, Bs)-Back,
closed_rest([], Rest)-[]),
!,
sort(Region0, Region).
region_foldl(X-Y,
closed_rest([X-Y|Closed0], Set)-Back,
closed_rest(Closed0, Set0)-Back0) :-
X0 is X-1, X1 is X + 1,
Y0 is Y-1, Y1 is Y + 1,
ord_intersection([X0-Y,X-Y0,X-Y1,X1-Y], Set, New, Set0),
append(New, Back0, Back).
This also "works". The fold leaves behind a choice point, because I haven't articulated the end condition as in fac_foldl/4 above, so I need a cut right after it (ugly).
The Questions
Is there a clean way of closing the list and removing the cut? In the factorial example, we know when to stop because we have additional information; however, in the second example, how do we notice that the back of the list should be the empty list?
Is there a hidden problem I am missing?
This looks like its somehow similar to the Implicit State with DCGs, but I have to admit I never quite got how that works; are these connected?
You are touching on several extremely interesting aspects of Prolog, each well worth several separate questions on its own. I will provide a high-level answer to your actual questions, and hope that you post follow-up questions on the points that are most interesting to you.
First, I will trim down the fragment to its essence:
essence(N) :-
foldl(essence_(N), [2|Back], Back, _).
essence_(N, X0, Back, Rest) :-
( X0 #< N ->
X1 #= X0 + 1,
Back = [X1|Rest]
; Back = []
).
Note that this prevents the creation of extremely large integers, so that we can really study the memory behaviour of this pattern.
To your first question: Yes, this runs in O(1) space (assuming constant space for arising integers).
Why? Because although you continuously create lists in Back = [X1|Rest], these lists can all be readily garbage collected because you are not referencing them anywhere.
To test memory aspects of your program, consider for example the following query, and limit the global stack of your Prolog system so that you can quickly detect growing memory by running out of (global) stack:
?- length(_, E),
N #= 2^E,
portray_clause(N),
essence(N),
false.
This yields:
1.
2.
...
8388608.
16777216.
etc.
It would be completely different if you referenced the list somewhere. For example:
essence(N) :-
foldl(essence_(N), [2|Back], Back, _),
Back = [].
With this very small change, the above query yields:
?- length(_, E),
N #= 2^E,
portray_clause(N),
essence(N),
false.
1.
2.
...
1048576.
ERROR: Out of global stack
Thus, whether a term is referenced somewhere can significantly influence the memory requirements of your program. This sounds quite frightening, but really is hardly an issue in practice: You either need the term, in which case you need to represent it in memory anyway, or you don't need the term, in which case it is simply no longer referenced in your program and becomes amenable to garbage collection. In fact, the amazing thing is rather that GC works so well in Prolog also for quite complex programs that not much needs to be said about it in many situations.
On to your second question: Clearly, using (->)/2 is almost always highly problematic in that it limits you to a particular direction of use, destroying the generality we expect from logical relations.
There are several solutions for this. If your CLP(FD) system supports zcompare/3 or a similar feature, you can write essence_/3 as follows:
essence_(N, X0, Back, Rest) :-
zcompare(C, X0, N),
closing(C, X0, Back, Rest).
closing(<, X0, [X1|Rest], Rest) :- X1 #= X0 + 1.
closing(=, _, [], _).
Another very nice meta-predicate called if_/3 was recently introduced in Indexing dif/2 by Ulrich Neumerkel and Stefan Kral. I leave implementing this with if_/3 as a very worthwhile and instructive exercise. Discussing this is well worth its own question!
On to the third question: How do states with DCGs relate to this? DCG notation is definitely useful if you want to pass around a global state to several predicates, where only a few of them need to access or modify the state, and most of them simply pass the state through. This is completely analogous to monads in Haskell.
The "normal" Prolog solution would be to extend each predicate with 2 arguments to describe the relation between the state before the call of the predicate, and the state after it. DCG notation lets you avoid this hassle.
Importantly, using DCG notation, you can copy imperative algorithms almost verbatim to Prolog, without the hassle of introducing many auxiliary arguments, even if you need global states. As an example for this, consider a fragment of Tarjan's strongly connected components algorithm in imperative terms:
function strongconnect(v)
// Set the depth index for v to the smallest unused index
v.index := index
v.lowlink := index
index := index + 1
S.push(v)
This clearly makes use of a global stack and index, which ordinarily would become new arguments that you need to pass around in all your predicates. Not so with DCG notation! For the moment, assume that the global entities are simply easily accessible, and so you can code the whole fragment in Prolog as:
scc_(V) -->
vindex_is_index(V),
vlowlink_is_index(V),
index_plus_one,
s_push(V),
This is a very good candidate for its own question, so consider this a teaser.
At last, I have a general remark: In my view, we are only at the beginning of finding a series of very powerful and general meta-predicates, and the solution space is still largely unexplored. call/N, maplist/[3,4], foldl/4 and other meta-predicates are definitely a good start. if_/3 has the potential to combine good performance with the generality we expect from Prolog predicates.
If your Prolog implementation supports freeze/2 or similar predicate (e.g. Swi-Prolog), then you can use following approach:
fac_list(L, N, Max) :-
(N >= Max, L = [Max], !)
;
freeze(L, (
L = [N|Rest],
N2 is N + 1,
fac_list(Rest, N2, Max)
)).
multiplication(X, Y, Z) :-
Z is Y * X.
factorial(N, Factorial) :-
fac_list(L, 1, N),
foldl(multiplication, L, 1, Factorial).
Example above first defines a predicate (fac_list) which creates a "lazy" list of increasing integer values starting from N up to maximum value (Max), where next list element is generated only after previous one was "accessed" (more on that below). Then, factorial just folds multiplication over lazy list, resulting in constant memory usage.
The key to understanding how this example works is remembering that Prolog lists are, in fact, just terms of arity 2 with name '.' (actually, in Swi-Prolog 7 the name was changed, but this is not important for this discussion), where first element represents list item and the second element represents tail (or terminating element - empty list, []). For example. [1, 2, 3] can be represented as:
.(1, .(2, .(3, [])))
Then, freeze is defined as follows:
freeze(+Var, :Goal)
Delay the execution of Goal until Var is bound
This means if we call:
freeze(L, L=[1|Tail]), L = [A|Rest].
then following steps will happen:
freeze(L, L=[1|Tail]) is called
Prolog "remembers" that when L will be unified with "anything", it needs to call L=[1|Tail]
L = [A|Rest] is called
Prolog unifies L with .(A, Rest)
This unification triggers execution of L=[1|Tail]
This, obviously, unifies L, which at this point is bound to .(A, Rest), with .(1, Tail)
As a result, A gets unified with 1.
We can extend this example as follows:
freeze(L1, L1=[1|L2]),
freeze(L2, L2=[2|L3]),
freeze(L3, L3=[3]),
L1 = [A|R2], % L1=[1|L2] is called at this point
R2 = [B|R3], % L2=[2|L3] is called at this point
R3 = [C]. % L3=[3] is called at this point
This works exactly like the previous example, except that it gradually generates 3 elements, instead of 1.
As per Boris's request, the second example implemented using freeze. Honestly, I'm not quite sure whether this answers the question, as the code (and, IMO, the problem) is rather contrived, but here it is. At least I hope this will give other people the idea what freeze might be useful for. For simplicity, I am using 1D problem instead of 2D, but changing the code to use 2 coordinates should be rather trivial.
The general idea is to have (1) function that generates new Open/Closed/Rest/etc. state based on previous one, (2) "infinite" list generator which can be told to "stop" generating new elements from the "outside", and (3) fold_step function which folds over "infinite" list, generating new state on each list item and, if that state is considered to be the last one, tells generator to halt.
It is worth to note that list's elements are used for no other reason but to inform generator to stop. All calculation state is stored inside accumulator.
Boris, please clarify whether this gives a solution to your problem. More precisely, what kind of data you were trying to pass to fold step handler (Item, Accumulator, Next Accumulator)?
adjacent(X, Y) :-
succ(X, Y) ;
succ(Y, X).
state_seq(State, L) :-
(State == halt -> L = [], !)
;
freeze(L, (
L = [H|T],
freeze(H, state_seq(H, T))
)).
fold_step(Item, Acc, NewAcc) :-
next_state(Acc, NewAcc),
NewAcc = _:_:_:NewRest,
(var(NewRest) ->
Item = next ;
Item = halt
).
next_state(Open:Set:Region:_Rest, NewOpen:NewSet:NewRegion:NewRest) :-
Open = [],
NewOpen = Open,
NewSet = Set,
NewRegion = Region,
NewRest = Set.
next_state(Open:Set:Region:Rest, NewOpen:NewSet:NewRegion:NewRest) :-
Open = [H|T],
partition(adjacent(H), Set, Adjacent, NotAdjacent),
append(Adjacent, T, NewOpen),
NewSet = NotAdjacent,
NewRegion = [H|Region],
NewRest = Rest.
set_region_rest(Ns, Region, Rest) :-
Ns = [H|T],
state_seq(next, L),
foldl(fold_step, L, [H]:T:[]:_, _:_:Region:Rest).
One fine improvement to the code above would be making fold_step a higher order function, passing it next_state as the first argument.

Prompt does not come back

I try to do some exercise - to represent numbers in "s representation" which means '0' is zero, s(0) is 1, s(s(0)) is 2 and so on.
I tried to write predicate for adding "s numbers":
the predicate s2int convert "s number" to int.
s2int(0, 0).
s2int(s(X), Y) :-
s2int(X, Y1),
Y is 1 + Y1.
add(X, Y, Z) :-
s2int(X, SX),
s2int(Y, SY),
s2int(Z, SZ),
SZ is SX + SY.
When I query add it writes the correct answer but the prompt does not come back.
What's the problem?
Your definition of add/3 works fine, and also terminates, if all three arguments are given. If you leave one of them as a variable, one of the goals s2int(XYZ, SXYZ) has then two uninstantiated variables as arguments. It describes thus an infinitely large set, whose complete enumeration takes infinitely long.
Not sure what you are after, but probably you want to define add/3 for successor arithmetics. You can do this, without resorting to the 0,1,2 integers! Try it! Otherwise search of successor-arithmetics.

Resources