Prolog: Wejście/Wyjście i funkcje wbudowane Autorzy: Szymon Bieniek Kacper Bieniek
Wejście/wyjście Do tej pory jedynym sposobem na dostarczenie informacji do programu Prolog’a było zadawanie pytań w zakresie określonej bazy. Dodatkowo Prolog odpowiadał jedynie w sposób „X= odpowiedź”, co w większości przypadków zupełnie wystarcza. Jednakże przydatnym jest napisanie programu Prolog’a, tak by sam z nami toczył konwersacje.
Stara metoda Weźmy przykładową bazę wydarzeń zawierającą daty i nagłówki event(1505,[’Euclid’,translated,into,’Latin’]). event(1510,[’Reuchlin-Pfefferkorn’,controversy]). event(1523,[’Christian’,`II`,flees,from,’Denmark’]). ?- event(1505,X). X=[Euclid, translated,into,Latin].
Stara Metoda cd. Reprezentacja nagłówka jako listy atomów ma przewagę w momencie wyszukiwania pod kątem konkretnego atomu. Stwórzmy komendę when(X,Y) która powiedzie się jeśli X jest wspomniany w roku Y when(X,Y) :- event(Y,Z), member(X,Z). ?- when(‚Denmark’, Data). Data=1523
Nowa metoda Teraz chcemy, by Prolog sam zapytał nas o której dacie chcielibyśmy się dowiedzieć, a następnie wyświetli nagłówek. By osiągnąć ten cel użyjemy wbudowanych funkcji Prologa, które odpowiadają za wejście i wyjście
Funkcja read Funkcja read zczyta kolejną linię wprowadzoną z klawiatury zakończoną kropką i spacją bądź enterem. hello1(Event) :-read(Date), event(Date,Event). ?-hello1(X). Po tym zapytaniu Prolog będzie czekać na datę, jako że jest on podana w funkcji read 1523. Prolog przeszuka bazę danych pod kątem podanej daty i zwróci nam nagłówek: X=[‚Christian’,’II’,flees,from,’Denmark’]?
Funkcja write i nl Funkcja write jest używana do wyświetlania zadanego parametru. Podobnie jak funkcja read, działa tylko raz. Jeśli funkcja się nie powiedzie dostaniemy unikalną wartość numeryczną. Funkcja nl (new line) jest używana do wymuszenia nowej linii na wszystkich kolejno wypisywanych elementach. Tak samo jak write, działa tylko raz. Wypisując elementy listy, warto zadbać o ich czytelność, co w przypadku listy list może być utrudnione.
Funkcja write i nl cd. Postaramy się wypisać listę list [1,2,[3,4],5,6] w następujący sposób 1 2 3 4 5 6 W tym celu zdefiniujemy dwie funkcje, spaces i pp(X,Y)
Funkcja write i nl cd. Funkcja spaces będzie kontrolować głębokość elementów w naszej liście i odpowiednio wypisywać ilość spacji spaces(0) :- !. spaces(N) :- write(` `), N1 is N -1, spaces(N1). Funkcja pp będzie wypisywać kolejne elementy listy w nowych linijkach biorąc pod uwagę ich głębokość pp([H|T], I) :- !, J is I + 3, pp(H,J), ppx(T,J), nl. pp(X,I) :- spaces(I), write (X), nl. ppx([],_). ppx([H|T],I) :- pp(H,I) ppx(T,I).
Funkcja write i nl cd. Teraz stworzymy funkcję phh która wypisze nam nagłówki wydarzeń oddzielając każdy atom spacją. phh([]) :- nl. phh([H|T]) :- write(H), spaces(0), phh(T). Dla przykładu wyświetlimy wszystkie nagłówki zawierające „England”: ?- event(_, L), member (`England`,L), phh(L).
Połączenie read i write Teraz, kiedy poznaliśmy obie funkcje read i write, możemy ich użyć do ulepszenia naszego programu hello2:- phh([`What`,date,do,you,`desire?`]), read(D), event(D,S), phh(S).
Czytanie znaków Znak to najmniejszy element która może być zczytana i wypisana przez program. Prolog jako znak rozumie atomy które posiadają dokładnie jeden element w nazwie, na przykład: `a`,`\n` czy ` `.
Czytanie znaków cd. Prolog posiada kilka wbudowanych funkcji do czytania znaków. Jedną z nich jest funkcja get_char(X). Funkcja ta pozwala nam na zczytanie znaku wprowadzonego za pomocą klawiatury. W zależności od programu wpisane znaki mogą być dla Prologa niedostępne do póki linia tekstu nie zostanie zakończona Enterem. Jeśli X jest już w użyciu, get_char(X) porównuje go z kolejnym znakiem i powodzi się (lub nie) w zależności od wyniku.
Czytanie znaków cd. Przykładowy kod który sprawdza drobne błędy: check_line(OK) :- get_char(X), rest_line(`\n`,X,OK). rest_line(_,`\n`,yes) :- !. rest_line(Last,Current,no) :- typing_error(Last,Current), !, get_char(New), rest_line(Current,New,_). rest_line(_,Current,OK) :- rest_line(Current,New,OK). typing_error(`q`,`w`). typing_error(`c`,`v`).
Czytanie znaków cd. Gdy check_line(X) zostaje wywołane, czyta wszystkie znaki do póki nie pojawi się `\n` (nowa linia). Podczas czytania znaki są porównywane z listą częstych błędów (typing_error). Przykładowo, wiemy że w języku angielskim litery takie jak „qw” i „cv” nigdy nie występują obok siebie w wyrazach. Zapytanie ?- check_line(X). Please could you enter your cvomment on the proposal Zwróci nam X=no
Pisanie znaków Wbudowana funkcja put_char(X)wypisze nam znak X. Funkcja zawsze się powiedzie, ale zmiennej nie można nadpisać. W przypadku nadpisania pojawi się błąd, a efektem ubocznym będzie wypisanie argumentu jako znaku. ?- put_char(`h`), put_char(`e`), put_char(`l`), put_char(`l`), put_char(`o`). hello
Pisanie znaków cd. Możemy wykorzystać funkcję put_char(X) do poprawy błędów wyłapanych przez check_line. W tym celu napiszemy funkcję correct_line correct_line :- get_char(X), corect_rest_line(`\n`,X). correct_rest_line(C,`\n`) :- !, put_char(C), nl. correct_rest_line(Last,Current) :- typing_correction(Last,Current,Corr), !, get_char(New), correct_rest_line(Corr,New). correct_rest_line(Last,Current) :- put_char(Last), correct_rest_line(Current,New). typing_correction(`q`,`w`,`q`). typing_correction(`c`,`v`,`c`).
Czytanie zdań Do czytania zdań użyjemy wbudowanej funkcji read_in. Funkcja ta tworzy listę wyrazów, które wchodzą w skład naszego zdania. Prolog rozumie wyraz jako ciąg znaków kończący się spacją. Znaki specjalne takie jak `,` `.` `;` `?` `!` są czytane jako osobne wyrazy. Funkcja uznaje `.` `?` `!` jako zakończenie zdania. Duże litery automatycznie konwertowane są na małe. ?-read_in(S). The man, who is old, saw Joe’s hat. S = [the,man,`,`,who,is,old,`,`,saw,`joe``s`,hat,`.`]
Czytanie i zapisywanie plików Jak na razie używaliśmy Prologa do czytania i wypisywania tekstu na jego konsoli. Teraz zajmiemy się czytaniem i wypisywaniem tekstu do pliku. By lepiej zrozumieć dalszą część wykładu warto pamiętać o kilku zasadach jakimi rządzą się pliki. W Prologu nazwy plików są reprezentowane przez atomy. Pliki mają określoną długość i na końcu każdego z nich jest znacznik end of file marker. W momencie gdy natrafi na niego get_char(X) albo read(X), X zostanie zapisany jako specjalny atom `end_of_file`.
Czytanie i zapisywanie plików cd. Prolog rozpoznaje aktualny strumień wejścia i aktualny strumień wyjścia, z których czyta całe wejście i wyjście. Zazwyczaj za aktualny strumień wejścia uznaje się klawiaturę, a za strumień wyjścia wyświetlacz. Można to tymczasowo zmienić podczas działania programu. Zanim uzyskamy dostęp do pliku należy utworzyć dla niego strumień . Możemy do tego użyć wbudowanej funkcji open. Jako dwa pierwsze argumenty podajemy nazwę pliku i atom read albo write w zależności czy chcemy czytać czy zapisać plik. Trzecim argumentem jest zmienna nazywająca nasz strumień danych.
Czytanie i zapisywanie plików cd. Przykładowo ?- open(`mojplik.pl`,read,X). Podpisuje pod X strumień którego możemy użyć do czytania pliku `mojplik.pl` ?-open(`output`,write,X). Podpisuje pod X strumień którego możemy użyć do zapisania pliku o nazwie `output`.
Czytanie i zapisywanie plików cd. Funkcja close zamyka strumień, przykładowo: program :- open(`mojplik.pl`,read,X), code_reading_from(X), close(X). Gdzie code_reading_from jest funkcją, która czyta kod z stumienia open(`output`,write,X), code_writing_to(X), Gdzie code_writing_to jest funkcją, która zapisuje kod ze strumienia.
Edycja aktualnego wejścia i wyjścia Program który utworzy nowy strumień X będzie musiał zrobić z nim następujące rzeczy: Ustawić aktualny strumień wejścia lub wyjścia jako X. Wywołać operacje close. Przekazać do innej części programu. Edycja aktualnego strumienia jest egzekwowana przez wbudowane funkcje set_input i set_output. Oba wymagają jednego argumentu, nazwy strumienia bądź atomu user_input/user_output. Efekty tych funkcji są nieodwracalne.
Edycja aktualnego wejścia i wyjścia cd. Wbudowane funkcje current_input i current_output pozwalają nam na dostęp do aktualnego strumienia. Daje nam to możliwość zapamiętania stanu strumienia przed edycją. program :- open(`mojplik.pl`,read,X), current_input(Stream), set_input(X), code_reading, close(X), set_input(Stream).
Edycja aktualnego wejścia i wyjścia cd. program :- open(`mojplik.pl`,write,X), current_output(Stream), set_output (X), code_writing, close(X), set_output(Stream).
Sprawdzanie pliku Czytanie i zapisywanie plików jest najbardziej pomocne gdy pracujemy z większą ilością elementów. W Prologu pliki mogą przechowywać programy. Jeśli tekst naszego programu jest zapisany w pliku, czytanie i zapisywanie go do bazy danych jest zwane sprawdzaniem pliku. Prolog posiada wbudowaną funkcję consult. W momencie wywołania, consult(X) (gdzie X to plik) zczyta wszystkie linie kodu. Prolog posiada również możliwość składania i czytania listy plików. ?- [file1,mapper,expert]. Działa to w taki sam sposób co: consultall([]). consultall([H|T]) :- consult(H), consultall(T).
Operatory Operatory w prologu posiadają trzy właściwości: pozycję, nadrzędność klasy, przywiązanie. Pozycja ma trzy opcje: infix, postfix,prefix (operator z dwoma argumentami jest pomiędzy nimi ; operator z jednym argumentem może wystąpić przed lub po nim). Nadrzędną klasą jest Integer, którego zasięg w Prologu wynosi od 1 do 1200. Nadrzędność klasy jest użyta by jednoznacznie określić klasę zmiennych w przypadku nie jasnego użycia nawiasów. Przywiązanie ujednolica działanie w momencie gdy są użyte w nim dwa operatory.
Operatory cd. W prologu przywiązujemy specjalny atom do operatora który okleśla jego pozycję i przywiązanie. Dla operatorów z dwoma argumentami możliwości są cztery: xfx xfy yfx yfy Dla operatorów stojących przed argumentem: fx fy Dla operatorów stojących za argumentem: xf yf Gdzie x i y to argumenty a f to operator.
Operatory cd. Argumenty są przedstawione za pomocą x i y gdyż argument może posiadać wewnątrz siebie kolejny operator. Na przykład: a + b + c Ma dwie możliwości: (a + b) + c albo a + (b + c)
Operatory cd. W prologu jeśli chcemy zadeklarować nasz operator używamy wbudowanej funkcji op. ?- op(Prec, Spec,Name). Dla przykładu, oto lista kilku domyślnie zadeklarowanych operatorów: ?- op(1200,xfx,`:-`). ?- op(1200,fx,`?-`). ?- op(1200,fx,`:-`). ?- op(700,xfx,`=`). ?- op(900,fy,`\+`). ?- op(500,yfx,`+`). ?- op(400,yfx,`*`).
Funkcje wbudowane Dodawanie nowych warunków: consult(X) gdzie X jest nazwą naszego pliku o rozszerzeniu .pl pozwala podmienić wszystkie warunki w zadanym pliku na jedeno twierdzenie jeśli chcemy ten efekt uzyskać na wielu plikach jednocześnie możemy się posłużyć listą w sposób: ?- [plik1,plik2,plik3]. co zastępuje dłuższą wersję: ?- consult(plik1),consult(plik2),consult(plik3).
Funkcje wbudowane cd. kolejnymi wbudowanymi twierdzeniami są true i fail true istnieje jedynie dla wygody fail pozwala na wymuszanie nawrotów w procesie poszukiwania rozwiązania, co pozwala w szczególności na znalezienie wszystkich rozwiązań problemu. ?- event(X,Y), phh(Y), fail.
Funkcje wbudowane cd. twierdzenie var(X) działa, jeśli X jest w danym momencie nieprzypisaną zmienną np.: var(X) zwraca true var(23) zwraca false X=Y, Y=23, var(X) zwraca false twierdzenie nonvar(X) jest przeciwieństwem funkcji var(X) a więc działa jeśli X NIE jest w danym momencie nieprzypisaną zmienną
Funkcje wbudowane cd. twierdzenie atom(X) działa jeśli w danym momencie X jest atomem Prologa, a więc: atom(23) zwraca false atom(apples) zwraca true atom(program.pl) zwraca true atom(X) zwraca false twierdzenie number(X) działa jeśli X jest w danym momencie liczbą twierdzenie atomic(X) działa jeśli X jest w danym momencie atomem bądź liczbą: atomic(X):-atom(X). atomic(X):-number(X).
Funkcje wbudowane cd. twierdzenie listing(A), gdzie A jest przypisane do zdefiniowanego warunku w pliku Prologa pozwala zobaczyć jaki jest proces działania danego polecenia jeśli w pliku zdefinujemy funkcje: dad(tom). son(tim). parent(tom,tim). child(X,Y):-parent(Y,X). to użycie listing(child). w kompilatorze wyrzuci nam na ekran child(X,Y):-parent(Y,X).
Funkcje wbudowane cd. twierdzenia asserta(X) i assertz(X), pozwalają dodać do pliku Prologa funkcje asserta dodaje funkcje na początku programu, a assertz na końcu X musi być wcześniej przypisany do jakiejś funkcji twierdzenie retract(X) pozwala usunąć z programu funkcje oczywiście nie możemy usuwać funkcji wbudowanych
Funkcje wbudowane cd. twierdzenie atom_chars(A,L) pozwala nam rozbić ciąg charów na tablice pojedyńczych elementów lub z pojedyńczych charów stworzyć ciąg np.: atom_chars(apple,X). X=[a,p,p,l,e] atom_chars(X,[a,p,p,l,e]) X=apple
Funkcje wbudowane cd. twierdzenie number_chars(A,L) pozwala nam rozbić liczbe na tablice pojedyńczych cyfr lub z pojedyńczych cyfr stworzyć liczbe np.: number_chars(123.5,X). X=['1','2','3','.','5'] number_chars(X,['1','2','3']) X=123
Funkcje wbudowane cd. operatory `,` `i` `;` służą do łączenia dwóch funkcji , służy do traktowania dwóch celów łącznie, czyli: X,Y jest spełnione jeśli X jest spełniony i Y jest spełniony ; służy do traktowania dwóch celów rozłącznie, czyli: X;Y jest spełnione jeśli X jest spełniony LUB Y jest spełniony
Funkcje wbudowane cd. Twierdzenie call(X), gdzie X to funkcja, jest spełnione jeśli X jest spełnione jest to funkcja bezużyteczna na obecnym etapie, jako że np.: call(member(a,X)) da nam ten sam rezultat co member(a,X) operator \+ jest rozumiany jako zaprzeczenie, czyli: \+X jest spełnione jeśli X nie jest spełniony operator X==Y przyrównuje do siebie elementy X i Y, jeśli się to uda, to funkcja została spełniona, w przeciwnym wypadku, funkcja zawodzi.
Funkcje wbudowane cd. twierdzenie get_char(X) zostaje spełnione jeśli X pasuje do pierwszego napotkanego symbolu na strumieniu wejścia, wykonuje się jednorazowo twierdzenie read(X) zostaje spełnione jeśli X pasuje do całego wprowadzonego zdania twierdzenie put_char(X) wypisuje pojedyńczy znak na ekran, wykonuje się jednorazowo skrót nl powoduje wstawienie nowej linii write(X) wypisuje X na ekran, gdzie X jest ciągiem znaków write_canonical(X) działa tak jak write(X) z wyjątkiem tego że ignoruje operatory
Funkcje wbudowane cd. open(X,Y,Z) otwiera plik, gdzie X to nazwa pliku, Y ustawia się na read jeśli plik ma być do odczytu, bądź na write jeśli ma być do edycji, Z nadaje lokalna nazwe dla tekstu zawartego w pliku w razie chęci działania na nim. close(X) służy do zamknięcia otwartego tekstu, gdzie w miejsce X podajemy nazwę Z z przykładu powyżej
Funkcje wbudowane cd. set_input(X) ustawia wejście na dany ciąg który jest przekazany poprzez X ( Z w open ) set_output(X) ustawia wyjście na dany ciąg który jest przekazany poprzez X ( Z w open ) current_input(X) jest spełniony jeśli nazwa ciągu na wejściu jest taka jak X current_output(X) jest spełniony jeśli nazwa ciągu na wyjściu jest taka jak X
Funkcje wbudowane cd. X+Y daje nam sume X i Y X-Y daje nam różnice X i Y X*Y daje nam iloczyn X i Y X/Y daje nam iloraz X i Y w postaci float X//Y daje nam iloraz X i Y w postaci int XmodY daje nam reszte z dzielenia X i Y X=:=Y sprawdzenie czy X i Y mają te same numeryczne wartości X=\=Y sprawdzenie czy X i Y mają różne numeryczne wartości X<Y sprawdzenie czy wartość X jest mniejsza od wartości Y X>Y sprawdzenie czy wartość X jest większa od wartości Y X>=Y sprawdzenie czy wartość X jest większa bądź równa wartości Y X=<Y sprawdzenie czy wartość X jest mniejsza bądź równa wartości Y
Funkcje wbudowane cd. spy P służy do ustawiania punktów zainteresowania w wybranych miejscach programu dzięki którym mamy wgląd wewnątrz funkcji debugging wyświetla liste wszystki punktów szpiega nodebug usuwa wszystkie punkty szpiega nospy usuwa podane przez użytkownika punkty szpiega