Как создать двунаправленные предикаты в Prolog?
в прологе, есть предикаты,двунаправленный (или даже "многовекторности"), в том смысле, что можно разделить набор переменных на входные и выходные переменные по-разному. Например, предикат number_string
действует как биекция в обоих направлениях:
number_string(N, "42"). % finds solution N = 42
number_string(42, S). % finds solution S = "42"
однако это замечательное свойство, по-видимому, не сохраняется соединением предложений. Например, рассмотрим следующие два предиката, которые просто переведите строки, как 3x^2
в части абстрактного дерева синтаксиса как 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).
оба предиката абсолютно одинаковы, за исключением того, что порядок определяющих предложений в правой части перевернут. Все пункты с правой стороны сами являются двунаправленными.
вот пример сессии:
?- 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
как предикаты работать только в одну сторону, хотя оба они, очевидно, bijections, что довольно печальный.
вопрос состоит из двух частей (есть два требования: простота в использовании, простота в обслуживании):
- есть ли надежный способ построения двунаправленных предикатов в Prolog?
- можно ли избежать дублирования кода (то есть поддерживать две версии: одну нормальную, одну обратную)?
может быть, есть какой-то флаг, который говорит "изменить порядок предложений, если это необходимо"?
в настоящее время я использую SWI-Prolog 7.1.x, если это уместно.
2 ответов
есть два способа решить эту проблему: либо вы используете дополнительный аргумент, указывающий, каким должен быть порядок, и реализуете верхний предикат, который выбирает, какой из ваших предикатов использовать на его основе, или вы пишете верхний предикат, который делает выбор, проверяя, какие аргументы свободны без необходимости внешней информации. В последнем вы, вероятно, будете использовать var/1
или nonvar/1
внелогические предопределенные предикаты; я широко использовал это в создании обратимых Грамматик определенного предложения, чтобы их можно было использовать для синтаксического анализа и генерации текста, таким образом избегая большой работы как при написании программ, так и при их обслуживании.
обновление в ответ на это дополнительное требование.
чтобы избежать дублирования кода, Вы должны попытаться определить общие части в оба варианта и определить предикаты для каждой части. Затем вы вызываете эти предикаты там, где это необходимо, вместо того, чтобы иметь их код во многих местах. Но это не очень полезно в вашем пример, который слишком мал: возможно, вы можете попробовать использовать списки различий и избегать конкатенаций, чтобы упростить свои предикаты.
систематически цепочки bijections [мой собственный ответ]
плюсы:
- производит двунаправленный сказуемое из цепочки биекция-предикаты.
- позволяет избежать дублирования кода.
- явная спецификация заказа не требуется.
недостатки:
- подавляет предупреждения о неиспользуемых свободных переменных
- использует "отражение" (может повлиять на производительность скомпилированного кода)
вот как мы можем связать несколько предикатов, которые, очевидно, 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: 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)).
можно также просто пройти Clauses
сразу, никакая дополнительная переменная не необходима. Это работает, как и ожидалось, в обоих направлениях:
parse_stuff("3x^2", T), write(T), nl,
parse_stuff(X, term(3,2)), write(X), nl
выдает:
term(3,2)
3x^2