Подтвердить что ты не робот

Какое использование имеет значение if_/3?

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

Этот предикат реализуется как таковой, любезно предоставленный @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.

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

4b9b3361

Ответ 1

В старомодном коде Пролога следующий шаблон возникает довольно часто:

predicate([], ...).
predicate([L|Ls], ...) :-
        condition(L),
        then(Ls, ...).
predicate([L|Ls], ...) :-
        \+ condition(L),
        else(Ls, ...).

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

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

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

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

predicate([], ...).
predicate([L|Ls], ...) :-
        (    condition(L) ->
             then(Ls, ...).
        ;    else(Ls, ...).
        )

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


Используя if_/3, вы можете записать это как:

predicate([], ...).
predicate([L|Ls], ...) :-
        if_(condition(L),
            then(Ls, ...),
            else(Ls, ...)).

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

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

цена этого довольно доступная: как отметил Борис в комментариях, вам нужно реализовать reification. У меня теперь есть некоторый опыт в этом, и я нашел это довольно легко с некоторой практикой.

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

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

Ответ 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: Пожалуйста, внимательно прочитайте код и взаимодействие верхнего уровня.