Suppose I have some expressions that look like a /\ b \/ c
. I would like to generate the truth table for this, something like:
a | b | c | a /\ b \/ c
---+----+----+-------------+-
F | F | F | F
F | F | T | T
F | T | F | F
F | T | T | T
T | F | F | F
T | F | T | T
T | T | F | T
T | T | T | T
A key idea here is to handle operators that are not already handled by is/2
, such as logical implication ->
. By the way, this question is derived from a post by reddit user u/emergenthoughts.
The code I have for this is as follows:
bool(0).
bool(1).
negate(1, 0).
negate(0, 1).
eval(Assignments, A, V) :- atom(A), memberchk(A=V, Assignments).
eval(Assignments, \+ E, V) :- eval(Assignments, E, NotV), negate(NotV, V).
eval(Assignments, E1 /\ E2, V) :-
eval(Assignments, E1, V1),
eval(Assignments, E2, V2),
V is V1 /\ V2.
eval(Assignments, E1 \/ E2, V) :-
eval(Assignments, E1, V1),
eval(Assignments, E2, V2),
V is V1 \/ V2.
eval(Assignments, E1 -> E2, V) :-
eval(Assignments, E1, V1),
V1 = 1 -> eval(Assignments, E2, V) ; V = 1.
generate_assignment(Variable, Variable=B) :- bool(B).
generate_assignments(Variables, Assignments) :-
maplist(generate_assignment, Variables, Assignments).
atoms_of_expr(A, A) :- atom(A).
atoms_of_expr(\+ E, A) :- atoms_of_expr(E, A).
atoms_of_expr(E1 /\ E2, A) :- atoms_of_expr(E1, A) ; atoms_of_expr(E2, A).
atoms_of_expr(E1 \/ E2, A) :- atoms_of_expr(E1, A) ; atoms_of_expr(E2, A).
atoms_of_expr(E1 -> E2, A) :- atoms_of_expr(E1, A) ; atoms_of_expr(E2, A).
table_for(E) :-
setof(A, atoms_of_expr(E, A), Variables),
write_header(Variables, E),
write_separator(Variables, E),
table_rest(Variables, E).
table_rest(Variables, E) :-
generate_assignments(Variables, Assignments),
eval(Assignments, E, Value),
write_assignments(Assignments, Value),
fail.
table_rest(_, _) :- true.
write_header([Var|Rest], E) :-
write(' '), write(Var), write(' | '), write_header(Rest, E).
write_header([], E) :- writeln(E).
write_separator([_|R], E) :- write('---+-'), write_separator(R, E).
write_separator([], _) :- write('-+-'), nl.
write_assignments([_=Var|Rest], Value) :-
write(' '), write(Var), write(' | '), write_assignments(Rest, Value).
write_assignments([], Value) :- writeln(Value).
This code produces the slightly worse than desired output, but I didn't want to bore you with a lot of formatting:
?- table_for(a/\b\/c).
a | b | c | a/\b\/c
---+----+----+--+-
0 | 0 | 0 | 0
0 | 0 | 1 | 1
0 | 1 | 0 | 0
0 | 1 | 1 | 1
1 | 0 | 0 | 0
1 | 0 | 1 | 1
1 | 1 | 0 | 1
1 | 1 | 1 | 1
true.
I believe this solution is fairly simple and I like it, but I'm often surprised in Prolog by what the real wizards are able to do so I thought I'd ask if there are significant improvements to be made here. atoms_of_expr/2
feels a bit like boilerplate, since it duplicates the traversal in eval/3
. I didn't see a way to use term_variables/2
instead because then I don't think I'd be able to actually supply the names the variables have or bind on them properly with memberchk/2
. Am I mistaken?