How to create bidirectional predicates in Prolog?
There are two ways to solve this: either you use an extra argument stating what the order should be and implement a top-predicate that chooses which of your predicates to use on the base of it, or you write a top-predicate that makes the choice by testing which arguments are free with no need for external information. In the latter you will probably use the var/1
or nonvar/1
extra-logical predefined predicates; I used extensively this in making Definite-Clause Grammars reversible, so that they could be used for parsing and for generating text, in that way avoiding a lot of work both in writing the programs and maintaining them.
UPDATE in answer to the additional requirement.
To avoid code duplication you should try to identify the common parts in both versions and define predicates for each part. You then call these predicates where needed instead of having their code in many places. But this is not really useful in your example that is too small: maybe you can try using difference lists and avoiding the concatenations to simplify your predicates.
Systematically chaining bijections [My own answer]
Advantages:
- Produces a bidirectional predicate from a chain of bijection-predicates.
- Avoids any code duplication.
- No explicit specification of the order is required.
Drawbacks:
- Suppresses warnings about unused free variables
- Uses "reflection" (might have impact on performance of compiled code)
Here is how we can chain multiple predicates that are obviously bijections:
% suppresses "Singleton variables" warning for
% a single variable
suppress_singleton_warning(_).
% calls all clauses in a list.
call_all([]).
call_all([F|G]) :-
call(F),call_all(G).
% If `X` is a closed term, calls all clauses `FS`
% in left-to-right order.
bijection(X, FS, Y) :-
suppress_singleton_warning(Y),
free_variables(X, XVars),
XVars == [],
call_all(FS).
% If `Y` is a closed term, calls all clauses `FS`
% in right-to-left order
bijection(X, FS, Y) :-
suppress_singleton_warning(X),
free_variables(Y, YVars),
YVars == [],
reverse(FS, RevFS),
call_all(RevFS).
Example usage:
% Example: parser/printer that works in both
% directions.
parse_stuff(String, term(Coeff, Pow)) :-
Clauses = [
string_concat(CoeffStr, MonomialStr, String),
string_concat("x^", PowStr, MonomialStr),
number_string(Coeff, CoeffStr),
number_string(Pow, PowStr)
],
bijection(String, Clauses, term(Coeff, Pow)).
One can also just as well pass the Clauses
directly, no extra variable is needed. This works as expected, in both directions:
parse_stuff("3x^2", T), write(T), nl,
parse_stuff(X, term(3,2)), write(X), nl
gives:
term(3,2)
3x^2