Prolog Language
Korzystanie z Modern Prolog
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ładadd(+X,+Y,-Z)
jestdet
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)
jestsemidet
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ładfactor(+X,-Y)
byłbymulti
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ładbetween(+X,+Y,-Z)
jestnondet
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
io
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
inondet
sięnondeterm
(multi
nadalmulti
).