reglas - ¿Cómo crear predicados bidireccionales en Prolog?
reglas en prolog (2)
En Prolog, hay predicados que son bidireccionales (o incluso "multidireccionales" ), en el sentido de que uno puede dividir el conjunto de variables en variables de entrada y de salida de muchas maneras diferentes. Por ejemplo, el predicado number_string
actúa como una biyección en ambas direcciones:
number_string(N, "42"). % finds solution N = 42
number_string(42, S). % finds solution S = "42"
Sin embargo, esta maravillosa propiedad no parece conservarse por la conjunción de cláusulas. Por ejemplo, considere los siguientes dos predicados, que simplemente traducen cadenas como 3x^2
en fragmentos de un term(3,2)
árbol sintáctico abstracto term(3,2)
:
parse_stuff(String, term(Coeff, Pow)) :-
string_concat(CoeffStr, MonomialStr, String),
string_concat("x^", PowStr, MonomialStr),
number_string(Coeff, CoeffStr),
number_string(Pow, PowStr).
write_stuff(String, term(Coeff, Pow)) :-
number_string(Pow, PowStr),
number_string(Coeff, CoeffStr),
string_concat("x^", PowStr, MonomialStr),
string_concat(CoeffStr, MonomialStr, String).
Ambos predicados son exactamente iguales, excepto que el orden de las cláusulas definitorias en el lado derecho se invierte. Todas las cláusulas en el lado derecho son bidireccionales.
Aquí hay una sesión de ejemplo:
?- parse_stuff("3x^2", X).
X = term(3, 2).
?- parse_stuff(X, term(3, 2)).
ERROR: string_concat/3: Arguments are not sufficiently instantiated
?- write_stuff(X, term(3, 2)).
X = "3x^2".
?- write_stuff("3x^2", X).
ERROR: number_string/2: Arguments are not sufficiently instantiated
Ambos predicados funcionan solo de una manera, aunque ambos son obviamente biyecciones, lo cual es bastante triste.
La pregunta consta de dos partes (hay dos requisitos: fácil de usar, fácil de mantener):
- ¿Hay alguna manera robusta de construir predicados bidireccionales en Prolog?
- ¿Es posible evitar la duplicación de código (es decir, mantener dos versiones: una normal, una invertida)?
Tal vez hay alguna bandera que dice "invertir el orden de las cláusulas si es necesario".
Actualmente estoy usando SWI-Prolog 7.1.x, si es relevante.
Hay dos formas de resolver esto: o bien utiliza un argumento adicional que indica cuál debe ser el orden e implementa un predicado superior que elige cuál de sus predicados usar en la base del mismo, o escribe un predicado superior que hace que el elección probando qué argumentos son gratuitos sin necesidad de información externa. En este último, probablemente utilizará los predicados predefinidos extra-lógicos var/1
o nonvar/1
; Usé mucho esto para hacer reversibles las Gramáticas Definite-Clause, de modo que pudieran usarse para analizar y generar texto, de esa forma evitando mucho trabajo tanto al escribir los programas como al mantenerlos.
ACTUALIZAR en respuesta al requisito adicional.
Para evitar la duplicación de código, debe intentar identificar las partes comunes en ambas versiones y definir predicados para cada parte. A continuación, llama a estos predicados donde sea necesario en lugar de tener su código en muchos lugares. Pero esto no es realmente útil en su ejemplo que es demasiado pequeño: tal vez puede intentar usar listas de diferencias y evitar las concatenaciones para simplificar sus predicados.
Encadenando sistemáticamente biyecciones [Mi propia respuesta]
Ventajas:
- Produce un predicado bidireccional de una cadena de predicados de biyección.
- Evita cualquier duplicación de código.
- No se requiere una especificación explícita de la orden.
Inconvenientes:
- Suprime las advertencias sobre variables libres no utilizadas
- Utiliza "reflexión" (podría tener un impacto en el rendimiento del código compilado)
Así es como podemos encadenar múltiples predicados que obviamente son biyecciones:
% 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).
Ejemplo de uso:
% 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)).
También se puede pasar las Clauses
directamente, no se necesita ninguna variable adicional. Esto funciona como se espera, en ambas direcciones:
parse_stuff("3x^2", T), write(T), nl,
parse_stuff(X, term(3,2)), write(X), nl
da:
term(3,2)
3x^2