Как я могу смоделировать мягкий разрез в Прологе?

Как я могу имитировать мягкую резку? *-> Т; E в ISO-прологе? У меня есть побочные эффекты, поэтому я не могу не вызывать его несколько раз.

За исключением последнего требования, я думаю, что следующее определение работает:

if_(I, T, E) :-
    not(not(I)) ->
    call((I, T));
    call((not(I), E)).

(На самом деле я использую пролог XSB; решение для XSB тоже было бы полезно для меня.)


person Ed McMan    schedule 15.11.2016    source источник
comment
Лучше используйте для этого if/3, как это делает SICStus. Кроме того, зачем вам нужна эта конструкция? Обратите внимание, что вы не получаете монотонность с ним. Он имеет те же недостатки, что и отрицание в стиле Пролога.   -  person false    schedule 16.11.2016
comment
Интересно, нельзя ли для этого использовать функциональные возможности таблиц XSB.   -  person jschimpf    schedule 17.11.2016


Ответы (4)


Да, мы можем реализовать это в ISO Prolog и даже в XSB, но не очень эффективно. Чтобы сделать это эффективным, вам понадобится «выборочное сокращение». Кроме того, XSB не реализует целые числа, соответствующие ISO, поэтому переполнение должно обрабатываться отдельно.

:- dynamic(if_counter/1).

if_counter(0).

:- dynamic(no_if_answer/1).
if(If_0, Then_0, Else_0) :-
   once(if_counter(Id)),
   Idx is Id+1,
   (  Idx > Id -> true
   ;  throw(error(representation_error(max_integer),
               'XSB misses ISO conforming integers'))
   ),
   retractall(if_counter(_)),
   asserta(if_counter(Idx)),
   asserta(no_if_answer(Id)),
   (  If_0,
      retractall(no_if_answer(Id)),
      Then_0
   ;  retract(no_if_answer(Id)) ->
      Else_0
   ).

Основной источник неэффективности заключается в том, что для определенного условия If_0 все еще остается точка выбора. Почти немыслимо мыслить, что реализация может прийти к выводу, что retract(no_if_answer(Id)) всегда будет терпеть неудачу после выполнения retractall(no_if_answer(Id)), но я сомневаюсь, что разработчики будут вкладывать средства в такие оптимизации. РЕДАКТИРОВАТЬ: причина, по которой это кажется маловероятным, заключается в том, что реализация должна гарантировать, что заявленные числа всегда растут.

Обратите внимание, что мягкий разрез создает неполноту точно так же, как и разрез. Рассмотреть возможность:

| ?- if(X = a, T = equal, T = not_equal).

X = a
T = equal;

no

Это явно пропускает ответ! Чтобы понять почему, возьмите X = b:

| ?- X = b, if(X = a, T = equal, T = not_equal).

X = b
T = not_equal;

no
| ?- if(X = a, T = equal, T = not_equal), X = b.

no % bad!!

Конъюнкция должна быть коммутативной (по модулю незавершения, ошибок, побочных эффектов).

Если вы заинтересованы в декларативно обоснованных условных выражениях, которые также очень эффективны и часто быстрее, чем их нечистые аналоги, рассмотрите if_/3. См. library(reif) для SICStus, который дает все правильные ответы:

| ?- if_(X = a, T = equal, T = not_equal).
X = a,
T = equal ? ;
T = not_equal,
prolog:dif(X,a) ? ;
no
person false    schedule 16.11.2016
comment
Если аргументы мягкого сокращения не оставляют точки выбора, мягкое сокращение также не должно оставлять точку выбора. См. поведение SWI и Jekejeke. - person Mostowski Collapse; 31.03.2019

Хорошо, давайте проявим творческий подход... По сути, вам нужен способ запомнить (через возврат), что условие If имело по крайней мере одно решение. Динамические предикаты для меня - нет-нет, но есть ли альтернативы? Итак, ISO-Prolog определяет один тип анонимного объекта, stream-term, который может быть (ab) использован для реализации флага без возможности возврата следующим довольно элегантным способом:

if(If, Then, Else) :-
    open(., read, S),
    (
        If,
        close(S, [force(true)]),
        Then
    ;
        catch(close(S), error(existence_error(stream,_),_), fail),   % fail if already closed
        Else
    ).

Мы закрываем поток, чтобы указать, что у If есть решение, и затем это обнаруживается попыткой закрытия в ветке else. Это работает отлично и без утечек в такой системе, как ECLiPSe. Однако многие системы (включая XSB) повторно используют идентификаторы закрытых потоков (что не запрещено ISO), что делает это решение непереносимым.

Но подождите, у потоков есть свойство position, которое можно установить и которое сохраняет свое значение при возврате! Используя этот прием, на XSB работает следующее:

if(If, Then, Else) :-
    % open('ReadableAndNonemptyFile', read, S),      % general ISO
    open(atom(a), read, S),                          % XSB (needs no file)
    stream_property(S, position(Zero)),
    get_char(S, _),
    (
        catch(If, Ball, (close(S),throw(Ball))),
        set_stream_position(S, Zero),
        Then

    ; stream_property(S, position(Zero)) ->
        close(S),
        fail
    ;
        close(S),
        Else
    ).

К сожалению, функция open(atom(...),...) специфична для XSB, для строгого ISO-Prolog вам нужен фиктивный файл...

person jschimpf    schedule 17.11.2016
comment
Это действительно умное решение. Когда я поместил его в свое приложение, оно попыталось открыть «a» дважды одновременно и потерпело неудачу. Я использую if рекурсивно, и в этом, как мне кажется, проблема. Есть ли способ открыть новый поток для каждого вызова if? - person Ed McMan; 18.11.2016
comment
Первый выдает permission_error(open,file,.) в XSB, system_error в SICStus, но GNU принимает это, но только 1023 раза... - person false; 18.11.2016
comment
@EdMcMan: он действительно открывает новый поток при каждом вызове, но, похоже, у XSB есть небольшое ограничение (4 в моем XSB 3.7) на открытые атомные потоки. Если вместо этого открыть файл, проблема исчезнет. - person jschimpf; 18.11.2016
comment
Последний работает только для 4 одновременных экземпляров в XSB. Тогда open(atom(a), read, S). является ошибкой. - person false; 18.11.2016

В вашем определении не реализована семантика мягкого вырезания: когда тест пройдет успешно, вы сможете вернуться к нему. Это полезная управляющая конструкция (я использую ее, например, для реализации коиндукции в Logtalk), но, к сожалению, ее нельзя реализовать переносимым способом на уровне Пролога и, конечно же, в рамках ограничений стандарта ISO Prolog. Хорошей новостью является то, что все больше систем Prolog реализуют эту управляющую конструкцию. К ним относятся, в произвольном порядке, SWI-Prolog, YAP, SICStus Prolog, GNU Prolog, CxProlog, ECLiPSe, Jekejeke Prolog и Ciao. Обратите внимание, однако, что хотя в некоторых системах используется оператор *->/2, в некоторых (SICStus Prolog и Ciao) используется предикат if/3 (в YAP есть оба). Кроме того, семантика различается в крайних случаях (дистрибутив Logtalk включает набор соответствия Prolog, который также проверяет вариант *->/2).

person Paulo Moura    schedule 16.11.2016
comment
Есть ли другие варианты использования (*->/2), кроме коиндукции? - person Mostowski Collapse; 31.03.2019

Проблема в том, что мягкое сокращение должно быть достаточно умным, оно не должно оставлять точку выбора, когда его аргументы не оставляют точки выбора.

Нет точки выбора в SWI-Prolog:

   Welcome to SWI-Prolog (threaded, 64 bits, version 8.1.4)

   ?- X=1 *-> Y=1; true.
   X = Y, Y = 1.
   ?- 

Нет точки выбора в Jekejeke Prolog:

   Jekejeke Prolog 3, Runtime Library 1.3.6

   ?- X=1 *-> Y=1; true.
   X = 1,
   Y = 1
   ?- 

Пока что ни одно из заведомо творческих решений здесь не архивирует это, поэтому ни одно из них не может с пользой заменить нативную реализацию.

Jekejeke Prolog выполняет проверка детерминированности, а затем удаляет точку выбора дизъюнкции. В противном случае он отмечает точку выбора дизъюнкции. Из модуля "логика":

:- set_predicate_property(;/2, sys_nobarrier).
A *-> B; C :- sys_local_cut, sys_soft_cond(A, B, C).

:- set_predicate_property(sys_soft_cond/3, sys_nobarrier).
sys_soft_cond(A, B, _) :- sys_safe(A), sys_soft_local_cut, B.                        
sys_soft_cond(_, _, C) :- C. 
person Mostowski Collapse    schedule 30.03.2019