Szukaj…


Wprowadzenie

Wiele nowoczesnych systemów Prolog jest w ciągłym rozwoju i dodało nowe funkcje w celu usunięcia klasycznych wad języka. Niestety, wiele podręczników Prologu, a nawet kursy dydaktyczne, wciąż przedstawiają tylko przestarzały prolog. Ten temat ma na celu zilustrowanie, w jaki sposób współczesny Prolog przezwyciężył niektóre problemy i dość nieokrzesaną składnię, która pojawia się w starszym Prologu i może być nadal wprowadzana.

CLP (FD) dla arytmetyki liczb całkowitych

Tradycyjnie Prolog wykonywał arytmetykę za pomocą operatorów is i =:= . Jednak kilka obecnych Prologów oferuje CLP (FD) (programowanie z ograniczeniami w domenach skończonych) jako czystszą alternatywę dla arytmetyki liczb całkowitych. CLP (FD) polega na przechowywaniu ograniczeń dotyczących wartości całkowitej i łączeniu ich razem w pamięci.

CLP (FD) jest rozszerzeniem w większości prologów, które go obsługują, dlatego należy je ładować jawnie. Po załadowaniu składnia #= może zastąpić oba is i =:= . Na przykład w SWI-Prolog:

?- X is 2+2.
X = 4.

?- use_module(library(clpfd)).
?- X #= 2+2.
X = 4.

W przeciwieństwie is , #= potrafi rozwiązywać proste równania i unify w obu kierunkach:

?- 4 is 2+X.
ERROR: is/2: Arguments are not sufficiently instantiated

?- 4 #= 2+X.
X = 2.

CLP (FD) zapewnia własną składnię generatora.

?- between(1,100,X).
X = 1;
X = 2;
X = 3...

?- X in 1..100.
X in 1..100.

Zauważ, że generator tak naprawdę nie działa: przechowywane jest tylko ograniczenie zakresu, gotowe do połączenia z nim późniejszych ograniczeń. Generator można zmusić do uruchomienia (i brutalnych ograniczeń) za pomocą predykatu label :

?- X in 1..100, label([X]).
X = 1;
X = 2;
X = 3..

Korzystanie z CLP może pozwolić na inteligentną redukcję przypadków brutalnej siły. Na przykład przy użyciu arytmetyki liczb całkowitych w starym stylu:

?- trace.
?- between(1,10,X), Y is X+5, Y>10.
...
Exit: (8) 6 is 1+5 ? creep
Call: (8) 6 > 10 ? creep
...
X = 6, Y = 11; ...

Prolog wciąż przechodzi przez wartości 1-5, nawet jeśli z danych warunków da się matematycznie udowodnić, że wartości te nie mogą być przydatne. Za pomocą CLP (FD):

?- X in 1..10, Y #= X+5, Y #> 10.
X is 6..10,
X+5 #= Y,
Y is 11..15.

CLP (FD) natychmiast dokonuje obliczeń matematycznych i opracowuje dostępne zakresy. Dodanie label([Y]) spowoduje, że X zapętli się tylko przez przydatne wartości 6..10. W tym przykładzie zabawki nie zwiększa to wydajności, ponieważ przy tak małym zakresie, jak 1-10, przetwarzanie algebry trwa tak długo, jak zrobiłaby to pętla; ale gdy przetwarzany jest większy zakres liczb, może to znacznie skrócić czas obliczeń.

Obsługa CLP (FD) jest zmienna między Prologami. Uznany najlepszy rozwój CLP (FD) znajduje się w SICStus Prolog, który jest komercyjny i drogi. SWI-Prolog i inne otwarte Prologi często mają pewne implementacje. Visual Prolog nie zawiera CLP (FD) w swojej standardowej bibliotece, chociaż dostępne są dla niego biblioteki rozszerzeń.

Forall zamiast pętli sterowanych awarią

Niektóre „klasyczne” podręczniki Prologu nadal używają mylącej i podatnej na błędy składni pętli, w której wykorzystuje fail konstrukcję błędów, aby wymusić wycofanie w celu zastosowania celu do każdej wartości generatora. Na przykład, aby wydrukować wszystkie liczby do określonego limitu:

fdl(X) :- between(1,X,Y), print(Y), fail.
fdl(_).

Zdecydowana większość współczesnych prologów nie wymaga już tej składni, zamiast tego zapewnia predykat wyższego rzędu, aby rozwiązać ten problem.

nicer(X) :- forall(between(1,X,Y), print(Y)).

Jest to nie tylko łatwiejsze do odczytania, ale jeśli cel, który mógłby zawieść, zostałby użyty zamiast wydruku , jego awaria zostałaby poprawnie wykryta i przekazana dalej - podczas gdy awarie bramek w pętli sterowanej awarią są mylone z wymuszoną awarią który napędza pętlę.

Visual Prolog ma niestandardowy składniowy cukier dla tych pętli w połączeniu z predykatami funkcji (patrz poniżej):

vploop(X) :- foreach Y = std::fromTo(1,X) do
                 console::write(X)
             end foreach.

Chociaż wydaje się to konieczne dla pętli, nadal przestrzega reguł Prologa: w szczególności każda iteracja foreach ma swój własny zakres.

Predykaty funkcyjne

Tradycyjnie w Prologu „funkcje” (z jednym wyjściem i powiązanymi danymi wejściowymi) były zapisywane jako zwykłe predykaty:

mangle(X,Y) :- Y is (X*5)+2.

Może to powodować trudność polegającą na tym, że jeśli predykat w stylu funkcji jest wywoływany wiele razy, konieczne jest „tymczasowe połączenie” zmiennych tymczasowych.

multimangle(X,Y) :- mangle(X,A), mangle(A,B), mangle(B,Y).

W większości Prologów można tego uniknąć, pisząc alternatywny operator infix, który ma być używany zamiast is który rozszerza wyrażenia, w tym funkcję alternatywną.

% Define the new infix operator
:- op(900, xfy, <-).

% Define our function in terms of the infix operator - note the cut to avoid
% the choice falling through
R <- mangle(X) :- R is (X*5)+2, !.

% To make the new operator compatible with is..
R <- X :-
    compound(X),            % If the input is a compound/function
    X =.. [OP, X2, X3],     % Deconstruct it
    R2 <- X2,               % Recurse to evaluate the arguments
    R3 <- X3,
    Expr =.. [OP, R2, R3],  % Rebuild a compound with the evaluated arguments
    R is Expr,              % And send it to is
    !.
R <- X :- R is X, !.        % If it's not a compound, just use is directly

Możemy teraz napisać:

multimangle(X,Y) :- X <- mangle(mangle(mangle(Y))).

Jednak niektóre współczesne Prologi idą dalej i oferują niestandardową składnię dla tego typu predykatów. Na przykład w Visual Prolog:

mangle(X) = Y :- Y = ((X*5)+2).
multimangle(X,Y) :- Y = mangle(mangle(mangle(X))).

Zauważ, że <- operator i powyższy predykat stylu funkcjonalnego nadal zachowują się jak relacje - legalne jest, że mają punkty wyboru i wykonują wielokrotną unifikację. W pierwszym przykładzie zapobiegamy temu przy użyciu cięć. W Visual Prolog normalne jest używanie składni funkcjonalnej do relacji, a punkty wyboru są tworzone w normalny sposób - na przykład cel X = (std::fromTo(1,10))*10 kończy się X = (std::fromTo(1,10))*10 X = 10 , X = 20, X = 30, X = 40 itd.

Deklaracje dotyczące przepływu / trybu

Podczas programowania w Prologu nie zawsze jest możliwe lub pożądane tworzenie predykatów, które ujednolicają się dla każdej możliwej kombinacji parametrów. Na przykład predykat between(X,Y,Z) który wyraża, że Z jest liczbowo między X i Y. Jest łatwo implementowany w przypadkach, w których wszystkie X, Y i Z są związane (albo Z jest między X a Y lub nie jest), lub gdzie X i Y są powiązane, a Z jest wolne (Z jednoczy się ze wszystkimi liczbami między X i Y lub orzeczenie nie powiedzie się, jeśli Y <X); ale w innych przypadkach, takich jak gdzie X i Z są związane, a Y jest wolny, istnieje potencjalnie nieskończona liczba unifikacji. Chociaż można to wdrożyć, zwykle tak nie jest.

Deklaracje przepływu lub deklaracje trybu umożliwiają wyraźny opis zachowania predykatów, gdy są wywoływane z różnymi kombinacjami powiązanych parametrów. W przypadku between deklaracja będzie:

%! between(+X,+Y,+Z) is semidet.
%! between(+X,+Y,-Z) is nondet. 

Każda linia określa jeden potencjalny wzorzec wywoływania dla predykatu. Każdy argument jest ozdobiony znakiem + aby wskazać przypadki, w których jest związany, lub - aby wskazać przypadki, w których nie jest (dostępne są również inne dekoracje dla bardziej złożonych typów, takich jak krotki lub listy, które mogą być częściowo powiązane). Słowo kluczowe, po to wskazuje zachowanie orzeczenia w tej sprawie i może być jedną z nich:

  • det jeśli predykat zawsze się powiedzie bez punktu wyboru. Na przykład add(+X,+Y,-Z) jest det ponieważ dodanie dwóch podanych liczb X i Y zawsze będzie miało dokładnie jedną odpowiedź.
  • semidet jeśli predykat się powiedzie lub nie powiedzie, bez punktu wyboru. Jak wyżej, between(+X,+Y,+Z) jest semidet ponieważ Z jest między X i Y lub nie jest.
  • multi jeśli predykat zawsze się powiedzie, ale może mieć punkty wyboru (ale także może nie mieć). Na przykład factor(+X,-Y) byłby multi ponieważ liczba zawsze ma co najmniej jeden współczynnik - sam - ale może mieć więcej.
  • nondet jeśli predykat może odnieść sukces z punktami wyboru lub nie. Na przykład between(+X,+Y,-Z) jest nondet ponieważ może istnieć kilka możliwych unifikacji Z względem liczb między X i Y, lub jeśli Y <X, to nie ma między nimi liczb, a predykat zawodzi.

Deklaracje przepływu / trybu można również łączyć z etykietami argumentów, aby wyjaśnić, co oznaczają terminy, lub z pisaniem na klawiaturze. Na przykład between(+From:Int, +To:Int, +Mid:Int) is semidet .

W czystych Prologach deklaracje przepływu i trybu są opcjonalne i są używane tylko do generowania dokumentacji, ale mogą być bardzo przydatne, aby pomóc programistom zidentyfikować przyczynę błędów wystąpienia.

W rtęci deklaracje przepływu i trybu (i typy) są obowiązkowe i są zatwierdzane przez kompilator. Zastosowana składnia jest taka jak powyżej.

W Visual Prolog deklaracje i typy przepływów i trybów są również obowiązkowe, a ich składnia jest inna. Powyższa deklaracja zostanie zapisana jako:

between : (int From, int To, int Mid) determ (i,i,i) nondeterm (i,i,o).

Znaczenie jest takie samo jak powyżej, ale z różnicami, które:

  • Deklaracje przepływu / trybu są oddzielone od deklaracji typu (ponieważ zakłada się, że przepływ / tryb dla pojedynczego predykatu nie będzie się różnił w przypadku przeciążenia typu);
  • i i o używane są do + i - i są dopasowane do parametrów w oparciu o szeregowanie;
  • Używane terminy są różne. det się procedure , semidet się determ i nondet się nondeterm ( multi nadal multi ).


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow