Inspired by an earlier question I tried to implement something that would enumerate the possibilities for a boolean expression. However, I'm having trouble with variable choice. Here's my intended outcome:
?- eval(X^Y, R).
R = 0^0;
R = 0^1;
R = 1^0;
R = 1^1;
no.
Here's my code:
:- op(200, yfx, ^).
split(V, R) :- var(V), R = 0.
split(V, R) :- var(V), R = 1.
split(X ^ Y, XP ^ YP) :- split(X, XP), split(Y, YP).
This already doesn't do what I want even for this simple case:
?- split(Y, R).
R = 0 ;
R = 1 ;
Y = _G269^_G270,
R = 0^0 ;
Y = _G269^_G270,
R = 0^1 ;
Y = _G269^ (_G275^_G276),
R = 0^ (0^0) ;
Y = _G269^ (_G275^_G276),
R = 0^ (0^1) ;
Y = _G269^ (_G275^ (_G281^_G282)),
R = 0^ (0^ (0^0)) .
So, I can see what the problem is here, which is that on the way through split(Y, YP)
Prolog has exhausted the first two clauses, so it winds up in split(X^Y, ...)
again, unifying my Y
with X'^Y'
, essentially. I am just not sure what I need to do to close off that path except at the outset where I have the structure ^/2
.
I'd also like this to work with nested structures, so I can't just eliminate the recursive processing of the branches.
Edit: without operators
If the op/3
is bothering you, consider this formulation:
eval(and(X,Y), R).
R = and(0,0);
R = and(0,1);
R = and(1,0);
R = and(1,1);
no.
This would be the code in that case:
split(V, R) :- var(V), R = 0.
split(V, R) :- var(V), R = 1.
split(and(X,Y), and(XP,YP)) :- split(X, XP), split(Y, YP).
Bear in mind I'd still like it to work with recursive formulations like and(and(X,Y),and(Y,Z))
etc.
At some point of time I wrote a very small program that seems to be more or less what you are looking for. I really hope I am not totally misreading your question. You can check this SWISH program for the whole program. There are a few differences, like using
f
andt
instead of 0 and 1, or usingand
andor
instead of^
andv
. However, the basic idea is the same as yours (I think).If I remove some of the unnecessary stuff for demonstrating, then this would be the program:
The main point here is that I have a pure relation, my
v/2
, which simply states that the value oftrue
istrue
and the value offalse
isfalse
. Then, I am using a mutually recursive definition for actually evaluating the boolean expressions, which eventually bottoms out atv/2
.I decided to "tag" the boolean variables myself, so instead of
X
you would have to write?X
. If I remember correctly, at the time I realized I need a way to explicitly say if I have a Boolean variable within an expression, or if the whole expression is still "unknown". In other words, this:?X
is eithert
orf
and this:Expr
can be?X
, ornot ?X
, or?X and ?Y
, or?X or ?Y
, ornot (?X and ?Y)
, and so on.Tagging the variable with
?X
is what avoids the "defaulty representation" is what @mat explained. Importantly, without explicitly tagging, I don't see a way for telling apart Boolean variables from Boolean expressions.Here is how you can use it:
Main problem: Defaulty representation
The core problem in this case is the defaulty representation you are using to represent Boolean expressions.
By "defaulty" I mean that you cannot clearly distinguish the cases by pattern matching: In your case, a variable
V
may denote either0
and1
, orA^B
.Due to this shortcoming, you cannot cleanly express a constraint of the form "the variable
X
stands only for one of the two propositional constants" within your program.A way out: Clean representation
A declarative way out is to use a clean representation instead.
For example, suppose we arbitrarily use
v/1
to distinguish variables that only denote the propositional constants, then we have:v(X)
to denote the propositional variableX
A^B
to denote a compound expression.Clearly, since the principal functors and arities are different (
v/1
vs.(^)/2
), we can distinguish the cases by pattern matching alone.With this new representation, your snippet becomes:
Example query:
Note that this still works in all directions, also in the most general case:
As a rule of thumb, once you have to use an extra-logical predicate like
var/1
in your code, there is little hope to retain its logical purity and monotonicity. Aim for clean representations to preserve these properties.Sometimes, it is unavoidable to use defaulty representations, for example, because you want to make the input easier for users. In such cases, aim to quickly convert them to clean ones before starting the actual reasoning.
There are already two good answers, a pure one and the shorter one using
term_variables/2
. I would still like to address a detail:If you don't want to perform the head unification with
X^Y
if the first argument is a variable, pull the unification out of the head. Nothing forces you to put the termX^Y
there! What you want to do is to only consider your last clause if the previous two clauses did not apply. The previous two clauses were guarded byvar/1
, so you can guard the last one withnonvar/1
:With this your examples work as intended:
Edit: As mat pointed out in the comments, using tests like
var/1
can get you into all kinds of trouble.One problem is that there are various levels of instantiation that you would have to check carefully: mat's example query of
split(1, R)
fails with the above code (it should succeed withR = 1
). This is because1
falls into thenonvar
case but does not unify with_^_
. If we're going this way, we must distinguish not just thevar
andnonvar
cases, but rathervar
,ground
, andnonvar
-but-not-ground
. This gets a bit messy.Another issue is the query
split(V, R), V = 1
, which is declaratively equivalent to the query above, but succeeds twice, including with the solutionR = V
. This is because this definition ofsplit/2
(deliberately) avoided introducing sharing between its arguments. Whether we do want that sharing or not depends on the specification.I won't try to give a fully robust solution with instantiation tests as my point was not that this is the best way to go.