Как создать двунаправленные предикаты в 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, что довольно печальный.

вопрос состоит из двух частей (есть два требования: простота в использовании, простота в обслуживании):

  1. есть ли надежный способ построения двунаправленных предикатов в Prolog?
  2. можно ли избежать дублирования кода (то есть поддерживать две версии: одну нормальную, одну обратную)?

может быть, есть какой-то флаг, который говорит "изменить порядок предложений, если это необходимо"?

в настоящее время я использую SWI-Prolog 7.1.x, если это уместно.


Jump перейти к моему собственному ответу ↓

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