Какая польза от if_/3?

Предикат if_/3 кажется довольно популярным среди немногих основных участников Prolog-части Stack Overflow.

Этот предикат реализован как таковой, благодаря @false:

if_(If_1, Then_0, Else_0) :-
   call(If_1, T),
   (  T == true -> call(Then_0)
   ;  T == false -> call(Else_0)
   ;  nonvar(T) -> throw(error(type_error(boolean,T),_))
   ;  /* var(T) */ throw(error(instantiation_error,_))
   ).

Однако я не смог найти четкого, простого и краткого объяснения того, что делает этот предикат и как его используют по сравнению, например, с классической конструкцией if-then-else Prolog if -> then ; else,

Большинство ссылок, которые я обнаружил, напрямую используют этот предикат и мало объясняют, почему он используется, что не специалист в Прологе может легко понять.

2 ответа

Решение

В старомодном коде Prolog такой шаблон возникает довольно часто:

предикат ([], ...).
предикат ([L|Ls], ...):-
        состояние (L),
        затем (Ls, ...).
предикат ([L|Ls], ...):-
        \ + состояние (L),
        еще (Ls, ...).

Я использую списки здесь в качестве примера, где это происходит (см., Например, include/3, exclude/3 и т.д.), хотя закономерность, конечно, встречается и в других местах.

Трагическое следующее:

  • Для созданного списка сопоставление с образцом может отличить первое предложение от оставшихся двух, но не может отличить второе предложение от последнего, поскольку оба они имеют '.'(_, _) в качестве основного функтора и арности их первого аргумента.
  • Условия, в которых применяются последние два пункта, очевидно, являются взаимоисключающими.
  • Таким образом, когда все известно, мы хотим получить эффективный детерминированный предикат, который не оставляет точек выбора, а в идеале даже не создает точек выбора.
  • Однако до тех пор, пока не все может быть безопасно определено, мы хотим извлечь выгоду из отслеживания, чтобы увидеть все решения, поэтому мы не можем позволить себе выполнить ни одно из положений.

Таким образом, существующие конструкции и языковые возможности в некотором роде не соответствуют выражению модели, которая часто встречается на практике. Поэтому на протяжении десятилетий казалось необходимым идти на компромисс. И вы можете сделать довольно хорошее предположение, в каком направлении обычно идут "компромиссы" в сообществе прологов: почти всегда жертвой правильности является эффективность в случае сомнений. В конце концов, кто заботится о правильных результатах, пока ваши программы быстры, верно? Поэтому до изобретения if_/3 это часто ошибочно записывалось как:

предикат ([], ...).
предикат ([L|Ls], ...):-
        (условие (L) ->
             затем (Ls, ...).; еще (Ls, ...).)

Ошибка в этом состоит, конечно, в том, что, когда элементы не достаточно созданы, тогда это может неправильно зафиксировать одну ветку, даже если обе альтернативы логически возможны. По этой причине использование if-then-else почти всегда декларативно неверно и в значительной степени мешает подходам декларативной отладки из-за нарушения самых элементарных свойств, которые мы ожидаем от чистых программ Prolog.


С помощью if_/3 Вы можете написать это как:

предикат ([], ...).
предикат ([L|Ls], ...):-
        if_(условие (L),
            затем (Ls, ...),
            еще (Ls, ...)).

и сохранить все желаемые аспекты. Это:

  • детерминированный, если все может быть безопасно решено
  • эффективный в том, что он даже не создает точек выбора
  • Завершите это тем, что вы никогда не делаете неверную фиксацию в одной конкретной ветке

Цена этого довольно доступная: как уже упоминал Борис в комментариях, вам необходимо осуществить реификацию. Теперь у меня есть некоторый опыт с этим, и мне было довольно легко с некоторой практикой.

Хорошие новости для всех: во многих случаях condition имеет форму (=)/2, или же (#=)/2, и первые даже корабли с library(reif) бесплатно

Для получения дополнительной информации см. Indexing dif/2 Ульриха Ноймеркеля и Стефана Краля!

Давайте попробуем решить простую проблему, используя if_/3; например, я попытаюсь разделить список (отсортированный по предикату p/2) в двух списках: префикс, в котором, для каждого элемента X, у нас есть p(X, true)и отдых (в котором, если список был отсортирован на p/2, мы бы хотели иметь p(X, false),

Я буду использовать библиотеку reif как здесь Итак, вот полный код моей программы:

:- use_module(reif).

pred_prefix(Pred_1, List, L_true, L_false) :-
        pred_prefix_aux(List, Pred_1, L_true, L_false).

pred_prefix_aux([], _, [], []).
pred_prefix_aux([X|Xs], Pred_1, True, False) :-
        if_(    call(Pred_1, X),
                (       True = [X|True0],
                        pred_prefix_aux(Xs, Pred_1, True0, False)
                ),
                (       True = [],
                        False = [X|Xs]
                )
        ).

Предикат, переданный этому мета-предикату, будет принимать два аргумента: первый - это текущий элемент списка, а второй - либо true или же false, В идеале этот предикат всегда будет успешным и не оставит позади точки выбора.

В первом аргументе if_/2предикат оценивается с помощью текущего элемента списка; вторым аргументом является то, что происходит, когда true; третий аргумент в том, что происходит, когда false,

С этим я могу разделить список в ведущих aс и отдых:

?- pred_prefix([X, B]>>(=(a, X, B)), [a,a,b], T, F).
T = [a, a],
F = [b].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,c,d], T, F).
T = [],
F = [b, c, d].

?- pred_prefix([X, B]>>(=(a, X, B)), [b,a], T, F).
T = [],
F = [b, a].

?- pred_prefix([X, B]>>(=(a, X, B)), List, T, F).
List = T, T = F, F = [] ;
List = T, T = [a],
F = [] ;
List = T, T = [a, a],
F = [] ;
List = T, T = [a, a, a],
F = [] .

Как вы можете избавиться от ведущих 0, например:

?- pred_prefix([X, B]>>(=(0, X, B)), [0,0,1,2,0,3], _, F).
F = [1, 2, 0, 3].

Конечно, это можно было бы написать гораздо проще:

drop_leading_zeros([], []).
drop_leading_zeros([X|Xs], Rest) :-
    if_(=(0, X), drop_leading_zeros(Xs, Rest), [X|Xs] = Rest).

Здесь я только что удалил все ненужные аргументы.

Если бы вам пришлось сделать это без if_/3, вам пришлось бы написать:

drop_leading_zeros_a([], []).
drop_leading_zeros_a([X|Xs], Rest) :-
    =(0, X, T),
    (   T == true -> drop_leading_zeros_a(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

Здесь мы предполагаем, что =/3 всегда будет успешным без точек выбора и T всегда будет либо true или же false,

И, если бы у нас не было =/3 либо вы бы написали:

drop_leading_zeros_full([], []).
drop_leading_zeros_full([X|Xs], Rest) :-
    (   X == 0 -> T = true
    ;   X \= 0 -> T = false
    ;   T = true, X = 0
    ;   T = false, dif(0, X)
    ),
    (   T == true -> drop_leading_zeros_full(Xs, Rest)
    ;   T == false -> [X|Xs] = Rest
    ).

что не идеально. Но теперь, по крайней мере, вы можете увидеть в одном месте, что на самом деле происходит.

PS: Пожалуйста, внимательно прочитайте код и взаимодействие на высшем уровне.

Другие вопросы по тегам