Mechanizm prologowy napotyka kłopoty z realizacją reguł z rekurencją lewostronną. Z rekurencją lewostronną mamy do czynienia wtedy, kiedy ten sam predykat występuje w głowie reguły i w pierwszym podcelu ciała. Przykład (patrz też zadanie 1):
rodzeństwo(piotr, paweł). rodzeństwo(A, B):- rodzeństwo(B, A).
Podanie celu niezgodnego z bazą, np. rodzeństwo(marek, X), spowoduje zapętlenie programu.
Przykładowe rozwiązanie:
rodzeństwo1(piotr, paweł). rodzeństwo(A, B):- rodzeństwo1(A, B). rodzeństwo(A, B):- rodzeństwo1(B, A).
Efektywniejszą metodą stosowania rekurencji w programach arytmetycznych jest użycie konstrukcji z akumulatorem. Odpowiada to zastąpieniu rekurencji iteracją, np. zamiast następującego programu:
silnia(1, 1) :- !. silnia(N, Wynik) :- N1 is N - 1, silnia(N1, Wynik1), Wynik is N * Wynik1.
można użyć programu:
silnia(N, Wynik) :- silnia_z_akumulatorem_i_licznikiem(N, 1, 1, Wynik). silnia_z_akumulatorem_i_licznikiem(N, N, Wynik, Wynik) :- !. silnia_z_akumulatorem_i_licznikiem(N, L, A, Wynik) :- L1 is L+1, A1 is A*L1, silnia_z_akumulatorem_i_licznikiem(N, L1, A1, Wynik).
Drugi argument predykatu silnia_z_akumulatorem_i_licznikiem pełni funkcję licznika zliczającego liczbę wykonanych pętli (od 1 do N), trzeci argument to akumulator przechowujący cząstkowy wynik, zaś czwarty argument służy do przekazania wyniku.
Najczęściej możliwe jest, by funkcję licznika pełnił jednocześnie argument wejściowy - wtedy liczba argumentów zmniejsza się o jeden, np. dla silni:
silnia(N, Wynik) :- silnia_z_akumulatorem(N, 1, Wynik). /* (1) */ silnia_z_akumulatorem(1, A, A) :- !. /* (2) */ silnia_z_akumulatorem(N, A, Wynik) :- /* (3) */ N1 is N - 1, A1 is A * N, silnia_z_akumulatorem(N1, A1, Wynik).
Tym razem drugi argument pełni rolę akumulatora, trzeci argument służy do przekazywania wyniku. Procedura silnia_z_akumulatorem działa w ten sposób, że dla wywołania silnia_z_akumulatorem(N, A, W) pod W zostanie podstawiona wartość wyrażenia N! * A. Przy stosowaniu tej techniki musimy podać:
Szczegółowe informacje na temat wszystkich predykatów wbudowanych dostępnych w SWI-Prologu można znaleźć w podręczniku do tego programu.
W Prologu dopuszczalne jest stosowanie alternatywy w celu uproszczenia zapisu (jak również poprawienia działania mechanizmu wnioskowania). Do wyrażania alternatywy służy operator ; (średnik). Następujące zapisy są równoważne:
A :- B. A :- C.
oraz
A :- (B; C).
Predykat call służy do wywołania predykatu, który jest argumentem innego predykatu. Przykład:
pokaż_rozwiązanie(Cel) :- call(Cel). ?- pokaż_rozwiązanie(syn(X, henryk)).
Podanie powyższego celu spowoduje wykonanie celu syn(X, henryk).
Predykat repeat służy do wymuszenia poszukiwania kolejnych rozwiązań. Jest to predykat, który zawsze się powodzi i wymusza "zmianę kierunku" podczas nawracania. Przykład:
/* czeka na naciśnięcie klawisza N lub T, inne klawisze są pomijane */ potwierdzenie(X):- repeat, get_single_char(Odp), ((Odp = 78; Odp = 110), X = no; (Odp = 84; Odp = 116), X = yes).
Przykład zastosowania predykatów call i repeat:
sesja :- repeat, write('Podaj cel'), nl, read(Cel), nl, szukaj_wszystkie_rozw(Cel). szukaj_wszystkie_rozw(Cel):- call(Cel), write('Znalezione rozwiązanie'), nl, write(Cel), nl, write('Czy podać następne?'), nl, get(Odp), nl, (Odp = 78; Odp = 110). szukaj_wszystkie_rozw(_) :- write('Brak innych rozwiazan'), nl, fail.
Poszukiwanie rozwiązań można śledzić poprzez podanie celu trace, a następnie podanie celu, którego osiągnięcie chcemy śledzić. Kolejne kroki obserwuje się naciskając klawisz Enter. Debugowanie można przerwać poprzez naciśnięcie n (koniec śledzenia) lub a (przerwanie działania). Cel notrace unieważnia cel trace. Można śledzić wybrane kroki poprzez podanie celu spy(predykat). Wówczas program zatrzymuje się przy napotkaniu podanego predykatu. Naciśnięcie l powoduje skok do następnej realizacji predykatu.
Do bazy danych można dołączyć nowe klauzule podczas wykonywania programu przy pomocy polecenia asserta(+Klauzula) lub assertz(+Klauzula). Różnica między nimi polega na tym, że polecenie asserta umieszcza klauzule przed wszystkimi klauzulami znajdującymi się już w bazie danych, natomiast assertz - za klauzulami. Przykłady:
asserta(ojciec(bronek, broncio)). asserta(syn(X, Y):- ojciec(Y,X)).
Predykat retract(+Klauzula) powoduje usunięcie klauzuli z bazy danych. Predykat abolish(+Predykat, +Arg) powoduje usunięcie z bazy danych wszystkich klauzul opisujących Predykat o arności (liczbie argumentów) Arg.
Zadanie 1 Podać przykład opisu relacji przechodniej (np. dwuargumentowej relacji przodek) z lewostronną rekurencją i bez niej.
Zadanie 2 Zdefiniować rekurencyjnie predykat potęga(A, B, C), gdzie C jest wynikiem potęgowania AB. Zdefiniować ten sam predykat przy użyciu konstrukcji z akumulatorem (z licznikiem lub bez).
Zadanie 3 Podane przykłady zastosowania instrukcji repeat wykonać z zasłonięciem i bez zasłonięcia instrukcji repeat.
Zadanie 4 Sprawdzić na wybranej przez siebie bazie, czy zapisy a) A :- B, C. A:- B, D. b) A:- B, (C; D) są równoważne (tzn., że przy obu zapisach cel A jest spełniony dla tych samych warunków). Który z zapisów jest efektywniejszy (tzn. wymaga mniej pracy od mechanizmu wnioskowania)? Odpowiedź znaleźć, wykorzystując mechanizm śledzenia wnioskowania.
Zadanie 5 Plik odmiana.pl zawiera program-bazę danych form fleksyjnych rzeczowników męskożywotnych (rzeczowników męskich oznaczających zwierzęta, uwaga: program nie zawsze generuje poprawne formy).
Zadanie A Na podstawie pliku baza_dan.pl napisać swoją bazę danych, np. bazę danych samochodów lub rozbudować bazę danych z przykładu o dodatkowe operacje.
Zadanie 6 Na podstawie pliku alkohol.pl napisać podobny program typu "zgadywanka", np. "Twoja ulubiona książka".
Zadanie B (dodatkowe) Podać a) predykat dwuargumentowy obliczający n-tą liczbę ciągu Fibbonacciego, b) predykat wykonujący to samo zadanie z licznikiem i akumulatorem.