2016-10-03 5 views
29

Предикат if_/3, кажется, fairly popular среди немногих основных вкладчиков в части Пролога переполнения стека.Какое использование делает if_/3?

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

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

+1

Вы должны быть экспертом в более чем Prolog, чтобы понять это ;-) Невозможно помочь и загадочное имя. Имейте в виду, что первый аргумент не может быть ничем: он должен быть предикатом, который детерминистически детерминистически и объединяет свой второй аргумент с «истинным» или «ложным» («reification»), иначе возникает ошибка. –

+1

PS: Я не говорю, что это не полезный предикат, но есть нечто большее, чем кажется на первый взгляд. Скрытие уродства под слоями косвенности является общей чертой стиля программирования Prolog, пропагандируемого «несколькими главными вкладчиками», о которых вы говорите. Надеюсь, что я не нахожу слишком негативным, я считаю, что хорошо иметь такие конструкции и пытаться применить их к существующим и новым проблемам. –

+2

PPS: В частности, изучите определение '=/3', прямо под' if_/3', [по вашей собственной ссылке] (http://stackoverflow.com/a/27358600/1812457), где вы видите, что требуется написать предикат, который хорошо сочетается с 'if_/3'. –

ответ

16

В старомодном кода Prolog, возникает следующая картина довольно часто:

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

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

Трагическое заключается в следующем:

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

Таким образом, существующие конструкции и языковые функции все никак не позволяют выразить шаблон, который часто встречается на практике. Поэтому в течение десятилетий казалось необходимым   компромисс. И вы можете сделать довольно хорошее предположение, в каком направлении «компромиссы» обычно идут в сообществе 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, ...)). 

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

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

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

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

Для получения дополнительной информации см. Indexing dif/2 от Ulrich Neumerkel и Stefan   Kral!

+2

Правильно ли получить от этого ответа, что всегда следует использовать 'if_ 'вместо' if -> then; else' (исключая конкретные ситуации), аналогично тому, как 'CLP (FD)' всегда следует использовать вместо низкоуровневой арифметики (исключая конкретные ситуации)? – Fatalize

+2

Я бы назвал это так: 'if -> then; else' приводит к ** неправильным ** программам почти во всех случаях. Единственная причина использовать эту экстра-логическую конструкцию - сделать программы более эффективными, очень часто ценой правильности. 'if_/3', с другой стороны, * сочетает * правильность с приемлемой эффективностью по очень доступной цене: она берет оверенность (часто это' (=)/3', которая уже включена в библиотеку) и немного изучая новую конструкцию. Лучшим способом, всегда, является использование шаблонов, где это возможно. Если это невозможно, 'if_/3' является * очень хорошей альтернативой! – mat

+2

@Fatalize: Пожалуйста, прочитайте [Раздел 2] (https://arxiv.org/abs/1607.01590), который называется «Декларативные пределы для файла Prolog if-then-else», чтобы ответить на ваш вопрос о традиционном if-then-else , – false

9

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

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

:- 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). 

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

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

+1

Для SWI существует [эта версия] (http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/swi/reif.pl). Однако учтите, что в SWI семантика мета-предикатов сильно нестабильна (в отличие от SICStus). – false