I was trying to practice Prolog, as suggested by my TA, I am trying to create the rule append3(A,B,C,D) which means D is the result of the append of A,B and C.
The definition of append(A,B,C) is given
append([],B,B).
append([X|A],B,[X|C]):-append(A,B,C).
So I simply wrote following, which makes sense to me, as the rule for append3
append3(A,B,C,D) :- append(A,B,X),append(X,C,D).
After, I tried some query, such as append3(A,B,C,[1,2,3]). Everything was fine in the beginning, it was giving me the right results. However, at one moment, I pressed ;, it went into an infinite loop trying to search another answer.
I am not really sure why this happens? I suppose that append3(A,B,C,D) is a very basic rule to define. Is there anything that I am missing or that I didn't consider?
How can I fix this problem?
Prolog's execution mechanism is pretty complex compared to command oriented programming languages a.k.a. imperative languages (short form: imps). Your TA has given you an excellent example where you can improve your mastery of Prolog.
First of all, you need to understand the termination behavior of append/3. The best way is via a failure-slice which helps you focus on the part that is relevant to termination. In this case it is:
append([],B,B) :- false.
append([X|A],B,[X|C]):-append(A,B,C), false.
This fragment or failure slice now terminates exactly in all cases where your original definition terminates! And since it is shorter, it makes understanding of termination a bit easier. Of course, this new program will no longer find answers like for append([a],[b],[a,b]) — it will fail in stead. But for termination alone it's perfect.
Now let's go through the arguments one by one:
argument needs to have a non-empty list element and will fail (and terminate) should the argument be [] or any other term. Non-termination may only occur with a partial list (that's a list with a variable instead of [] at the end like [a,b|L]) or just a variable.
argument is just the variable B. There is no way how this B might be different to any term. Thus, B has no influence on termination at all. It is termination neutral.
argument is essentially the same as the first argument. In fact, those arguments look quite symmetrical although they describe different things.
To summarize, if the first or the last argument is a (fully instantiated) list, append/3 will terminate.
Note that I just said if and not iff. After all, the first and third argument are a bit connected to each other via the variable X. Let's ignore that for this analysis.
Now, to a failure slice of your definition.
append3(A,B,C,D) :- append(A,B,X), false, append(X,C,D).
Note that D does no longer occur in the visible part! Therefore, the fourth argument has no influence on termination of this fragment. And since X occurs for the first time, only A has an influence on its termination. Therefore, if A is just a variable (or a partial list), the program will not terminate! No need to look any further. No need to look at screens full of traces.
To fix this for a query like your query append3(A,B,C,[1,2,3])., D has to influence the first goal somewhat.
One possible fix suggested by another answer would be to exchange the goals:
append3(A,B,C,D) :- append(X,C,D), false, append(A,B,X).
Let's look at the variables of append(X,C,D)! C is termination neutral, X occurs for the first time and thus has no influence on termination at all. Only D may make this goal terminate. And thus queries like append3([1],[2],[3], D) will now loop! What a bargain, exchanging non-termination in one case for another!
To make your program work for both cases, the first goal of append/3 must contain both D and at least one of A, B or C. Try to find it yourself! Here is the spoiler:
append3(A, B, C, D) :- append(A, BC, D), append(B, C, BC).
Now the first goal terminates if either A or D is a (fully instantiated) list. The second goal requires either B or BC to be a list. And BC comes from the first goal, which is a list if D is one.
Thus append3(A, B, C, D) terminates_if b(A), b(B) ; (D).
See another answer for more on termination, failure slices, and a technique I have not mentioned here, which is termination inference.
And, note that there are still more cases where the definitions terminate, although they are rather obscure like append([a|_],_,[b|_]) or append3([a|_],_,_,[b|_])) which both fail (and thus terminate) although they only have partial lists. Because of this it's terminates_if and not terminates_iff.
You need to flip the predicates append(A, B, X), append(X, C, D). Since all three variables A, B, and X are unbound in append(A, B, X) prolog tries to satisfy it and then constrain it with append(X, C, D) which will always fail after giving you existing solutions. So it enters an infinite loop.
Try just executing append(A, B, X). all of them unbound. Prolog should show you an infinite sequence of solutions, these fail in the next clause append(X, C, D).
Flip the predicates and you should be fine.
append3(A, B, C, D) :-
append(T, C, D),
append(A, B, T).
Related
I have produced the following code.
list_reverse([],[]).
list_reverse([X],[X]).
list_reverse(Ls,[R|Rs]) :-
last_elem(Ls,R),
without_last_elem(Ls,Next),
list_reverse(Next,Rs).
last_elem([E],E).
last_elem([_|Xs],E) :-
last_elem(Xs,E).
without_last_elem([X,_|[]],[X|[]]).
without_last_elem([X|T0],[X|T1]) :-
without_last_elem(T0,T1).
Swipl:
?- list_reverse([1,2,3],X).
X = [3, 2, 1] ;
false.
This is exactly what I want.
However if I go in the opposite direction I get success, followed by non-termination.
?- list_reverse(X,[1,2,3]).
X = [3, 2, 1] ;
C-c C-cAction (h for help) ? a
abort
% Execution Aborted
What I am struggling to understand is why I first get a correct solution for X. Is my program correct or not?
I am not worried about reversing a list as much as I am about this pattern of getting a correct solution followed by non-termination. It is a pattern I have already come across a few times.
I am [worried] about this pattern of getting a correct solution followed by non-termination.
This is due to the very specific notion of (universal) termination in Prolog. In other programming languages termination is a much simpler beast (still an undecidable beast nevertheless). If, say, a function returns then it terminates (for that case). But in Prolog, producing an answer is not the end as there might be further solutions or just an unproductive loop. In fact, it's best not to consider your query ?- list_reverse(X,[1,2,3]). but rather the following instead.
?- list_reverse(X,[1,2,3]), false.
In this manner all distracting answers are turned off. The only purpose of this query is now either to show termination or non-termination.
After that,
you can either try to follow Prolog's precise execution path but that is as insightful as staring into a car's gearbox when you are lost (the gears caused you to move into the place where you are lost thus they are somehow the cause...). Or, you take a step back, and consider related program fragments (called slices) that share certain properties with your original program. For termination, a failure-slice helps you to better understand what is at stake. In your case consider:
list_reverse([],[]) :- false.
list_reverse([X],[X]) :- false.
list_reverse(Ls,[R|Rs]) :-
last_elem(Ls,R), false,
without_last_elem(Ls,Next),
list_reverse(Next,Rs).
last_elem([E],E) :- false.
last_elem([_|Xs],E) :-
last_elem(Xs,E), false.
?- list_reverse(X,[1,2,3]), false.
Since this failure slice does not terminate, also your original program doesn't terminate! And, it is much easier to reason here in this smaller fragment. If you want to fix the problem, you need to modify something in the visible part. Otherwise you will keep being stuck in a loop.
Note that none of the facts is part of the loop. Thus they are irrelevant for non-termination.
Also note that in list_reverse/2 the variable Rs is never used in the visible part. Thus Rs has no influence on termination! Please note that this is a proof of that property already. Does this mean that the second argument of list_reverse/2 has no influence on termination? What do you think?
The last_elem/2 can keep constructing larger lists, that all should be rejected. But you thus get stuck in an infinite loop.
We can make a function that works with accumulator, and iterates over both the two lists concurrently. That means that once the left or right list is exhausted, no more recursive calls will be made:
reverse(L1, L2) :-
reverse(L1, [], L2, L2).
reverse([], L, L, []).
reverse([H|T], L1, R, [_|T2]) :-
reverse(T, [H|L1], R, T2).
Here the [H|T] and [_|T2] pattern thus both pop the first item of the list, and we only match if both lists are exhausted.
This is probably the most trivial implementation of a function that returns the length of a list in Prolog
count([], 0).
count([_|B], T) :- count(B, U), T is U + 1.
one thing about Prolog that I still cannot wrap my head around is the flexibility of using variables as parameters.
So for example I can run count([a, b, c], 3). and get true. I can also run count([a, b], X). and get an answer X = 2.. Oddly (at least for me) is that I can also run count(X, 3). and get at least one result, which looks something like X = [_G4337877, _G4337880, _G4337883] ; before the interpreter disappears into an infinite loop. I can even run something truly "flexible" like count(X, A). and get X = [], A = 0 ; X = [_G4369400], A = 1., which is obviously incomplete but somehow really nice.
Therefore my multifaceted question. Can I somehow explain to Prolog not to look beyond first result when executing count(X, 3).? Can I somehow make Prolog generate any number of solutions for count(X, A).? Is there a limitation of what kind of solutions I can generate? What is it about this specific predicate, that prevents me from generating all solutions for all possible kinds of queries?
This is probably the most trivial implementation
Depends from viewpoint: consider
count(L,C) :- length(L,C).
Shorter and functional. And this one also works for your use case.
edit
library CLP(FD) allows for
:- use_module(library(clpfd)).
count([], 0).
count([_|B], T) :- U #>= 0, T #= U + 1, count(B, U).
?- count(X,3).
X = [_G2327, _G2498, _G2669] ;
false.
(further) answering to comments
It was clearly sarcasm
No, sorry for giving this impression. It was an attempt to give you a synthetic answer to your question. Every details of the implementation of length/2 - indeed much longer than your code - have been carefully weighted to give us a general and efficient building block.
There must be some general concept
I would call (full) Prolog such general concept. From the very start, Prolog requires us to solve computational tasks describing relations among predicate arguments. Once we have described our relations, we can query our 'knowledge database', and Prolog attempts to enumerate all answers, in a specific order.
High level concepts like unification and depth first search (backtracking) are keys in this model.
Now, I think you're looking for second order constructs like var/1, that allow us to reason about our predicates. Such constructs cannot be written in (pure) Prolog, and a growing school of thinking requires to avoid them, because are rather difficult to use. So I posted an alternative using CLP(FD), that effectively shields us in some situation. In this question specific context, it actually give us a simple and elegant solution.
I am not trying to re-implement length
Well, I'm aware of this, but since count/2 aliases length/2, why not study the reference model ? ( see source on SWI-Prolog site )
The answer you get for the query count(X,3) is actually not odd at all. You are asking which lists have a length of 3. And you get a list with 3 elements. The infinite loop appears because the variables B and U in the first goal of your recursive rule are unbound. You don't have anything before that goal that could fail. So it is always possible to follow the recursion. In the version of CapelliC you have 2 goals in the second rule before the recursion that fail if the second argument is smaller than 1. Maybe it becomes clearer if you consider this slightly altered version:
:- use_module(library(clpfd)).
count([], 0).
count([_|B], T) :-
T #> 0,
U #= T - 1,
count(B, U).
Your query
?- count(X,3).
will not match the first rule but the second one and continue recursively until the second argument is 0. At that point the first rule will match and yield the result:
X = [_A,_B,_C] ?
The head of the second rule will also match but its first goal will fail because T=0:
X = [_A,_B,_C] ? ;
no
In your above version however Prolog will try the recursive goal of the second rule because of the unbound variables B and U and hence loop infinitely.
I am currently trying to learn some basic prolog. As I learn I want to stay away from if else statements to really understand the language. I am having trouble doing this though. I have a simple function that looks like this:
if a > b then 1
else if
a == b then c
else
-1;;
This is just very simple logic that I want to convert into prolog.
So here where I get very confused. I want to first check if a > b and if so output 1. Would I simply just do:
sample(A,B,C,O):-
A > B, 1,
A < B, -1,
0.
This is what I came up with. o being the output but I do not understand how to make the 1 the output. Any thoughts to help me better understand this?
After going at it some more I came up with this but it does not seem to be correct:
Greaterthan(A,B,1.0).
Lessthan(A,B,-1.0).
Equal(A,B,C).
Sample(A,B,C,What):-
Greaterthan(A,B,1.0),
Lessthan(A,B,-1.0),
Equal(A,B,C).
Am I headed down the correct track?
If you really want to try to understand the language, I recommend using CapelliC's first suggestion:
sample(A, B, _, 1) :- A > B.
sample(A, B, C, C) :- A == B.
sample(A, B, _, -1) :- A < B.
I disagree with CappeliC that you should use the if/then/else syntax, because that way (in my experience) it's easy to fall into the trap of translating the different constructs, ending up doing procedural programming in Prolog, without fully grokking the language itself.
TL;DR: Don't.
You are trying to translate constructs you know from other programming languages to Prolog. With the assumption that learning Prolog means essentially mapping one construct after the other into Prolog. After all, if all constructs have been mapped, you will be able to encode any program into Prolog.
However, by doing that you are missing the essence of Prolog altogether.
Prolog consists of a pure, monotonic core and some procedural adornments. If you want to understand what distinguishes Prolog so much from other programming languages you really should study its core first. And that means, you should ignore those other parts. You have only so much attention span, and if you waste your time with going through all of these non-monotonic, even procedural constructs, chances are that you will miss its essence.
So, why is a general if-then-else (as it has been proposed by several answers) such a problematic construct? There are several reasons:
In the general case, it breaks monotonicity. In pure monotonic Prolog programs, adding a new fact will increase the set of true statements you can derive from it. So everything that was true before adding the fact, will be true thereafter. It is this property which permits one to reason very effectively over programs. However, note that monotonicity means that you cannot model every situation you might want to model. Think of a predicate childless/1 that should succeed if a person does not have a child. And let's assume that childless(john). is true. Now, if you add a new fact about john being the parent of some child, it will no longer hold that childless(john) is true. So there are situations that inherently demand some non-monotonic constructs. But there are many situations that can be modeled in the monotonic part. Stick to those first.
if-then-else easily leads to hard-to-read nesting. Just look at your if-then-else-program and try to answer "When will the result be -1"? The answer is: "If neither a > b is true nor a == b is true". Lengthy, isn't it? So the people who will maintain, revise and debug your program will have to "pay".
From your example it is not clear what arguments you are considering, should you be happy with integers, consider to use library(clpfd) as it is available in SICStus, SWI, YAP:
sample(A,B,_,1) :- A #> B.
sample(A,B,C,C) :- A #= B.
sample(A,B,_,-1) :- A #< B.
This definition is now so general, you might even ask
When will -1 be returned?
?- sample(A,B,C,-1).
A = B, C = -1, B in inf..sup
; A#=<B+ -1.
So there are two possibilities.
Here are some addenda to CapelliC's helpful answer:
When starting out, it is sometimes easy to mistakenly conceive of Prolog predicates functionally. They are either not functions at all, or they are n-ary functions which only ever yield true or false as outputs. However, I often find it helpful to forget about functions and just think of predicates relationally. When we define a predicate p/n, we're describing a relation between n elements, and we've named the relation p.
In your case, it sounds like we're defining conditions on an ordered triplet, <A, B, C>, where the value of C depends upon the relation between A and B. There are three relevant relationships between A and B (here, since we are dealing with a simple case, these three are exhaustive for the kind of relationship in question), and we can simply describe what value C should have in the three cases.
sample(A, B, 1.0) :-
A > B.
sample(A, B, -1.0) :-
A < B.
sample(A, B, some_value) :-
A =:= B.
Notice that I have used the arithmetical operator =:=/2. This is more specific than ==/2, and it lets us compare mathematical expressions for numerical equality. ==/2 checks for equivalence of terms: a == a, 2 == 2, 5+7 == 5+7 are all true, because equivalent terms stand on the left and right of the operator. But 5+7 == 7+5, 5+7 == 12, A == a are all false, since it are the terms themselves which are being compared and, in the first case the values are reversed, in the second we're comparing +(5,7) with an integer and in the third we're comparing a free variable with an atom. The following, however, are true: 2 =:= 2, 5 + 7 =:= 12, 2 + 2 =:= 4 + 0. This will let us unify A and B with evaluable mathematical expressions, rather than just integers or floats. We can then pose queries such as
?- sample(2^3, 2+2+2, X).
X = 1.0
?- sample(2*3, 2+2+2, X).
X = some_value.
CapelliC points out that when we write multiple clauses for a predicate, we are expressing a disjunction. He is also careful to note that this particular example works as a plain disjunction only because the alternatives are by nature mutually exclusive. He shows how to get the same exclusivity entailed by the structure of your first "if ... then ... else if ... else ..." by intervening in the resolution procedure with cuts. In fact, if you consult the swi-prolog docs for the conditional ->/2, you'll see the semantics of ->/2 explained with cuts, !, and disjunctions, ;.
I come down midway between CapelliC and SQB in prescribing use of the control predicates. I think you are wise to stick with defining such things with separate clauses while you are still learning the basics of the syntax. However, ->/2 is just another predicate with some syntax sugar, so you oughtn't be afraid of it. Once you start thinking relationally instead of functionally or imperatively, you might find that ->/2 is a very nice tool for giving concise expression to patterns of relation. I would format my clause using the control predicates thus:
sample(A, B, Out) :-
( A > B -> Out = 1.0
; A =:= B -> Out = some_value
; Out = -1.0
).
Your code has both syntactic and semantic issues.
Predicates starts lower case, and the comma represent a conjunction. That is, you could read your clause as
sample(A,B,C,What) if
greaterthan(A,B,1.0) and lessthan(A,B,-1.0) and equal(A,B,C).
then note that the What argument is useless, since it doesn't get a value - it's called a singleton.
A possible way of writing disjunction (i.e. OR)
sample(A,B,_,1) :- A > B.
sample(A,B,C,C) :- A == B.
sample(A,B,_,-1) :- A < B.
Note the test A < B to guard the assignment of value -1. That's necessary because Prolog will execute all clause if required. The basic construct to force Prolog to avoid some computation we know should not be done it's the cut:
sample(A,B,_,1) :- A > B, !.
sample(A,B,C,C) :- A == B, !.
sample(A,B,_,-1).
Anyway, I think you should use the if/then/else syntax, even while learning.
sample(A,B,C,W) :- A > B -> W = 1 ; A == B -> W = C ; W = -1.
I'd like someone to explain this procedure if possible (from the book 'learn prolog now'). It takes two numerals and adds them together.
add(0,Y,Y).
add(s(X),Y,s(Z)) :- add(X,Y,Z).
In principle I understand, but I have a few issues. Lets say I issue the query
?- add(s(s(0)), s(0), R).
Which results in:
R = s(s(s(0))).
Step 1 is the match with rule 2. Now X becomes s(0) and Y is still s(0). However Z (according to the book) becomes s(_G648), or s() with an uninstantiated variable inside it. Why is this?
On the final step the 1st rule is matched which ends the recursion. Here the contents of Y somehow end up in the uninstantiated part of what was Z! Very confusing, I need a plain english explanation.
First premises:
We have s(X) defined as the successor of X so basically s(X) = X+1
The _G### notation is used in the trace for internal variables used for the recursion
Let's first look at another definition of addition with successors that I find more intuitive:
add(0,Y,Y).
add(s(A),B,C) :- add(A,s(B),C).
this does basically the same but the recursion is easier to see:
we ask
add(s(s(0)),s(0),R).
Now in the first step prolog says thats equivalent to
add(s(0),s(s(0)),R)
because we have add(s(A),B,C) :- add(A,s(B),C) and if we look at the question A = s(0) and B=s(0). But this still doesn't terminate so we have to reapply that equivalency with A=0 and B=s(s(0)) so it becomes
add(0,s(s(s(0))),R)
which, given add(0,Y,Y). this means that
R = s(s(s(0)))
Your definition of add basically does the same but with two recursions:
First it runs the first argument down to 0 so it comes down to add(0,Y,Y):
add(s(s(0)),s(0),R)
with X=s(0), Y = s(0) and s(Z) = R and Z = _G001
add(s(0),s(0),_G001)
with X = 0, Y=s(0) and s(s(Z)) = s(G_001) = R and Z = _G002
add(0,s(0),_G002)
So now it knows that _G002 is s(0) from the definition add(0,Y,Y) but has to trace its steps back so _G001 is s(_G002) and R is s(_G001) is s(s(_G002)) is s(s(s(0))).
So the point is in order to get to the definition add(0,Y,Y) prolog has to introduce internal variables for a first recursion from which R is then evaluated in a second one.
If you want to understand the meaning of a Prolog program, you might concentrate first on what the relation describes. Then you might want to understand its termination properties.
If you go into the very details of a concrete execution as your question suggests, you will soon be lost in the multiplicity of details. After all, Prolog has two different interlaced control flows (AND- and OR-control) and in addition to that it has unification which subsumes parameter passing, assignment, comparison, and equation solving.
Brief: While computers execute a concrete query effortlessly for zillions of inferences, you will get tired after a screenful of them. You can't beat computers in that. Fortunately, there are better ways to understand a program.
For the meaning, look at the rule first. It reads:
add(s(X),Y,s(Z)) :- add(X,Y,Z).
See the :- in between? It is meant to symbolize an arrow. It is a bit unusual that the arrow points from right-to-left. In informal writing you would write it rather left-to-right. Read this as follows:
Provided, add(X,Y,Z) is true, then also add(s(X),Y,s(Z)) is true.
So we assume that we have already some add(X,Y,Z) meaning "X+Y=Z". And given that, we can conclude that also "(X+1)+Y=(Z+1)" holds.
After that you might be interested to understand it's termination properties. Let me make this very brief: To understand it, it suffices to look at the rule: The 2nd argument is only handed further on. Therefore: The second argument does not influence termination. And both the 1st and 3rd argument look the same. Therefore: They both influence termination in the same manner!
In fact, add/3 terminates, if either the 1st or the 3rd argument will not unify with s(_).
Find more about it in other answers tagged failure-slice, like:
Prolog successor notation yields incomplete result and infinite loop
But now to answer your question for add(s(s(0)), s(0), R). I only look at the first argument: Yes! This will terminate. That's it.
Let's divide the problem in three parts: the issues concerning instantiation of variables and the accumulator pattern which I use in a variation of that example:
add(0,Y,Y).
add(s(X),Y,Z):-add(X,s(Y),Z).
and a comment about your example that uses composition of substitutions.
What Prolog applies in order to see which rule (ie Horn clause) matches (whose head unifies) is the Unification Algorithm which tells, in particular, that if I have a variable, let's say, X and a funtor, ie, f(Y) those two term unify (there is a small part about the occurs check to...check but nevermind atm) hence there is a substitution that can let you convert one into another.
When your second rule is called, indeed R gets unified to s(Z). Do not be scared by the internal representation that Prolog gives to new, uninstantiated variables, it is simply a variable name (since it starts with '_') that stands for a code (Prolog must have a way to express constantly newly generated variables and so _G648, _G649, _G650 and so on).
When you call a Prolog procedure, the parameters you pass that are uninstantiated (R in this case) are used to contain the result of the procedure as it completes its execution, and it will contain the result since at some point during the procedure call it will be instantied to something (always through unification).
If at some point you have that a var, ie K is istantiated to s(H) (or s(_G567) if you prefer), it is still partilally instantiated and to have your complete output you need to recursively instantiate H.
To see what it will be instantiated to, have a read at the accumulator pattern paragraph and the sequent one, tho ways to deal with the problem.
The accumulator pattern is taken from functional programming and, in short, is a way to have a variable, the accumulator (in my case Y itself), that has the burden to carry the partial computations between some procedure calls. The pattern relies on recursion and has roughly this form:
The base step of the recursion (my first rule ie) says always that since you have reached the end of the computation you can copy the partial result (now total) from your accumulator variable to your output variable (this is the step in which, through unification your output var gets instantiated!)
The recursive step tells how to create a partial result and how to store it in the accumulator variable (in my case i 'increment' Y). Note that in the recursive step the output variable is never changed.
Finally, concerning your exemple, it follows another pattern, the composition of substitutions which I think you can understand better having thought about accumulator and instantiation via unification.
Its base step is the same as the accumulator pattern but Y never changes in the recursive step while Z does
It uses to unify the variable in Z with Y by partially instantiating all the computation at the end of each recursive call after you've reached the base step and each procedure call is ending. So at the end of the first call the inner free var in Z has been substituted by unification many times by the value in Y.
Note the code below, after you have reached the bottom call, the procedure call stack starts to pop and your partial vars (S1, S2, S3 for semplicity) gets unified until R gets fully instantiated
Here is the stack trace:
add(s(s(s(0))),s(0),S1). ^ S1=s(S2)=s(s(s(s(0))))
add( s(s(0)) ,s(0),S2). | S2=s(S3)=s(s(s(0)))
add( s(0) ,s(0),S3). | S3=s(S4)=s(s(0))
add( 0 ,s(0),S4). | S4=s(0)
add( 0 ,s(0),s(0)). ______|
(Pardon if my terminology is wrong... I'm new to Prolog.)
Suppose you have a series of symbols appearing in some unknown number of predicates.
f1(a, b, c, d).
f2(b, b, c).
...
fn(b, d, e).
Later--at runtime--you realize that terms a and b are the same, and you wish to merge them or replace one of them with the other. In other words, I would like to either:
Make a = b
Replace all instances of a with b
Replace a and b with a new symbol (made through gensym/2)
...or anything else that accomplishes this
... where I do not know which predicates use these terms.
Atoms that start with upper case letters are variables. The first step then is to use A and B. If at some point you decide two variables are actually equal, you just say it A = B. The process of stating logically that one thing = another is "unification".
e.g.
veryDifferentOrTheSame(A,B) :- veryDifferent(A,B).
veryDifferentOrTheSame(A,B) :- A = B.
Of course, unification won't always work. a(X) = b(X) will fail.
This all implies that when the code was written, you knew that you weren't sure A=B.
You can also dynamically assert clauses at runtime. Declaring a clause as dynamic and using assera or assertz.
But if you state:
iOwn(goldfish).
iOwnFish :- iOwn(fish).
and then want to make that work by saying "in my universe fish = goldfish", then you're in strange territory.