2016-10-03 16 views
29

Predikat if_/3 wydaje się być jednym z niewielu głównych współtwórców w części Prolog przepełnienia stosu.Jakie zastosowanie ma if_/3?

To orzeczenie jest zaimplementowany jako takiej, dzięki uprzejmości @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,_)) 
    ). 

Jednak nie udało mi się znaleźć jasne, proste i zwięzłe wyjaśnienie co to orzeczenie, a co użytku w porównaniu do np klasyczny konstrukt If-then-else Prologa if -> then ; else.

Większość linków, które znalazłem bezpośrednio, wykorzystuję ten predykat i dostarczam niewiele wyjaśnień, dlaczego jest on używany, a który nie jest ekspertem w Prologu.

+1

Musisz być ekspertem w więcej niż Prolog, aby to zrozumieć ;-) Nazwa tajemnicza też nie pomaga. Należy pamiętać, że pierwszy argument nie może być po prostu niczym: musi to być predykat, który sukcesywnie rozstrzygnie się i zintegruje swój drugi argument z "prawdziwym" lub "fałszem" ("reifikacja"), w przeciwnym razie zostanie zgłoszony błąd. –

+1

PS: Nie mówię, że to nie jest użyteczny predykat, ale jest w tym coś więcej, niż na pierwszy rzut oka. Ukrywanie brzydoty pod warstwami pośrednimi jest wspólną cechą stylu programowania Prolog, promowaną przez "kilku głównych autorów", o których mówisz. Mam nadzieję, że nie uważam się za zbyt negatywny, myślę, że dobrze jest mieć takie konstrukcje i próbować zastosować je do istniejących i nowych problemów. –

+2

PPS: W szczególności przestudiuj definicję '=/3', tuż poniżej' if_/3', [z twojego własnego linku] (http://stackoverflow.com/a/27358600/1812457), gdzie zobaczysz co wystarczy napisać predykat, który dobrze pasuje do 'if_/3'. –

Odpowiedz

16

W staromodnym kodu Prolog, pojawia się następujący wzór dość często:

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

używam list tutaj jako przykład gdzie to występuje (patrz na przykład include/3, exclude/3 etc.), chociaż wzór oczywiście występuje również gdzie indziej.

Tragiczna jest następujący:

  • Na liście instancja, pasujące do wzorca można wyróżnić pierwszy klauzulę od pozostałego   dwa, ale nie można rozróżnić drugi z ostatniego   jednego ponieważ zarówno mają '.'(_, _) jako główny funktor i arion ich pierwszego argumentu.
  • Warunki, w których obowiązują dwie ostatnie klauzule, to oczywiście wykluczająca się wzajemnie.
  • Tak więc, gdy wszystko jest znane, chcemy uzyskać efektywną, deterministyczny orzecznik że nie pozostawia wyboru punktów, a najlepiej nawet nie tworzyć punkty wyboru.
  • Jednak, o ile nie wszystko może być bezpiecznie określone, chcemy skorzystać z Backtracking zobaczyć wszystkie rozwiązania, więc nie możemy sobie pozwolić, aby zobowiązać się do jednej z klauzul.

Podsumowując, istniejące konstrukcje i funkcje językowe w pewien sposób nie spełniają oczekiwań, co często pojawia się w praktyce. Dlatego od dziesięcioleci wydawało się, że konieczne jest uzyskanie kompromisu   . I możesz zgadnąć, w którym kierunku "kompromisy" zwykle idą w społeczności Prolog: Prawie niezmiennie, poprawność jest poświęcana dla wydajności w razie wątpliwości. W końcu, kogo obchodzą poprawne wyniki, o ile twoje programy są szybkie, prawda?Dlatego do czasu wynalezienia if_/3, to było często błędnie zapisać jako:

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

pomyłkę w to oczywiście, że gdy elementy są nie wystarczająco instancja, to może nieprawidłowo zobowiązują się do jednego oddziału, nawet jeśli alternatywy są logicznie możliwe. Z tego powodu, używanie if-then-else jest prawie zawsze błędne deklaratywnie i stoi masowo na drodze deklaratywnego podejścia do debugowania z powodu jego naruszenia najbardziej elementarnych właściwości, jakich oczekujemy od czystych programów Prolog.


Korzystanie if_/3, można napisać to jako:

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

i zachowują wszystkie pożądane aspekty. To jest:

  • deterministyczny czy wszystko może być bezpiecznie postanowił
  • wydajny w tym, że nawet nie tworzyć punkty wybór
  • kompletny w które nigdy nieprawidłowo zobowiązać się do jedna konkretna gałąź.

cena tego jest dość przystępnej cenie: Boris wspomniano w komentarzach, trzeba wdrożyć reifikacji. Mam pewne doświadczenia z tym i okazało się to dość łatwe z pewną praktyką.

Dobre wieści każdy: W wielu przypadkach condition ma postać (=)/2 lub (#=)/2, a nawet pierwszych statków z library(reif)dla   wolnego.

Aby uzyskać więcej informacji, zobacz Indexing dif/2 autorstwa Ulricha Neumerkela i Stefana   Kral!

+2

Czy to jest prawidłowe, aby uzyskać od tej odpowiedzi, że należy zawsze używać 'if_ 'zamiast' if -> then; else' (z wyłączeniem konkretnych sytuacji), podobnie do tego, jak powinno się zawsze używać "CLP (FD)" zamiast arytmetyki niskiego poziomu (z wyłączeniem konkretnych sytuacji)? – Fatalize

+2

Powiedziałbym to tak: 'if -> then; else' prowadzi do ** złych ** programów w prawie wszystkich przypadkach. Jedynym powodem, dla którego warto skorzystać z tej dodatkowej konstrukcji, jest sprawienie, by programy były bardziej wydajne, bardzo często za cenę poprawności. 'if_/3', z drugiej strony, * łączy * poprawność z akceptowalną wydajnością, w bardzo przystępnej cenie: wymaga reifikacji (często jest to' (=)/3', która jest już zawarta w bibliotece) i trochę nauki nowego konstruktu. Zawsze najlepszym sposobem jest stosowanie dopasowywania wzorców tam, gdzie to możliwe. Jeśli to nie jest możliwe, 'if_/3' jest * bardzo * dobrą alternatywą! – mat

+2

@Fatalizowanie: przeczytaj [Sekcja 2] (https://arxiv.org/abs/1607.01590), która nazywa się "Deklaratywnymi ograniczeniami prologu, jeśli-potem-else", aby odpowiedzieć na twoje pytanie o tradycyjne . – false

9

Spróbujmy rozwiązać prosty problem za pomocą if_/3; na przykład spróbuję podzielić listę (posortowaną według predykatu p/2) na dwie listy: prefiks, w którym dla każdego elementu X mamy p(X, true) i resztę (w którym, jeśli lista została posortowana na p/2, musielibyśmy p(X, false)

będę korzystać z biblioteki reif jak here tak, tu jest kompletny kod mojego programu:..

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

przekazany do tej meta-orzecznika zajmie dwa argumenty predykatu: the pierwszy to bieżący element listy, a drugi to true lub false. Idealnie, ten predykat zawsze się powiedzie i nie pozostawi za sobą punktów wyboru.

W pierwszym argumencie z if_/2 predykat jest oceniany za pomocą bieżącego elementu listy; drugim argumentem jest to, co dzieje się, gdy true; trzecim argumentem jest to, co dzieje się, gdy false.

Dzięki temu mogę podzielić listę w prowadzeniu a s i reszta:

?- 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 = [] . 

Jak można pozbyć się wiodącym 0 dla przykładu:

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

Oczywiście, może to zostały napisane znacznie prostsze:

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

Tutaj właśnie usunąłem wszystkie niepotrzebne arg .

Jeśli trzeba by zrobić to bezif_/3, byś musiał napisać:

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

Tutaj zakładamy, że =/3 rzeczywiście zawsze uda bez punktów wyboru i T zawsze będzie albo true lub false.

A jeśli nie mamy =/3 albo, można napisać:

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

który nie jest idealny. Ale teraz przynajmniej możesz sam zobaczyć, w jednym miejscu, co się właściwie dzieje.

PS: Przeczytaj uważnie kod i interakcje na najwyższym poziomie.

+1

Dla SWI istnieje [ta wersja] (http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/swi/reif.pl). Należy jednak zauważyć, że w SWI semantyka meta-predykatów jest wysoce niestabilna (w przeciwieństwie do SICStus). – false

Powiązane problemy