Paradygmaty Programowania
Transkrypt
Paradygmaty Programowania
Paradygmaty Programowania Wykład 8, Programowanie w logice (wprowadzenie) dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 1 Anegdota Jak odpowiedzają programiści pytani o drogę? Ot, zobaczmy sami: Piszący w Pascalu (język proceduralny): Pójdzie pan do tego skrzyżowania, skręci pan w prawo w ulice: Mickiewicza, dalej ulicą: Prusa, a potem skręci pan w lewo w: Żeromskiego i już pan jest na miejscu. Piszący w Javie lub C# (języki obiektowe): Weź Pan taksówkę. Piszący w Haskellu (język funkcyjny): Dojdzie pan do tego skrzyżowania, a dalej się pan zapyta. Piszący w Prologu (język logiki): Widzi pan tamten wysoki budynek z wielkim czerwonym szyldem? To tam. Taki właśnie jest język logiki: opisujemy co chcemy rozwiązać, a nie jak rozwiązać - problem rozwiązuje za nas sam język. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 2 Wprowadzenie Programowanie w logice ogólnie rzecz biorąc polega na zapisywaniu stwierdzeń używając rachunku predykatów pierwszego rzędu. Wyniki powstają jako rezultat (automatycznego) wnioskowania. Języki programowania w logice nazywane są także językami deklaratywnymi, gdyż programy w nich pisane w odróżnieniu od języków imperatywnych nie składają się z podstawień i sterowania przepływem, a z deklaracji. Jedynym rozpowszechnionym i stosowanym językiem umożliwiającym tego typu programowanie jest Prolog. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 3 Rachunek predykatów pierwszego rzędu Posługujemy się rachunkiem predykatów w takiej formie w jakiej jest on zaimplementowany w Prologu. A zatem kilka podstawowych pojęć: Term to funktor (symbol stanowiący nazwę relacji) z listą parametrów atomowych, np. człowiek(jerzy), lubi(adam, jabłko). Stała jest szczególnym przypadkiem termu (bezparametrowym), np. jerzy, adam, jabłko. Stwierdzenie to jeden lub więcej termów połączonych spójnikami: ¬ negacja, ∨ alternatywa, ∧ koniunkcja, ⇔ równoważność i ⇒ implikacja W stwierdzeniach mogą pojawiać się zmienne (np. X, Y) związane kwantyfikatorami (uniwersalnym ∀ lub egzystencjalnym ∃). dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 4 Klauzule W Prologu nie używa się kwantyfikatorów; w zamian za to stwierdzenia zapisuje się w postaci klauzul. Ogólna postać klauzuli jest następująca: A1 ∧ A2 ∧ ... ∧ Am ⇒ B1 ∨ B2 ∨ ... ∨ Bn gdzie Ai i Bi to termy. I tak: Wyrażenie A1 ∧ A2 ∧ ... ∧ Am to poprzednik klauzuli. Wyrażenie B1 ∨ B2 ∨ ... ∨ Bn to następnik klauzuli. Każde stwierdzenie można zapisać w formie klauzuli - istnieje algorytm, który to robi. Prawa strona klauzuli nazywa się często głową, a lewa ciałem klauzuli. W Prologu używa się klauzul w postaci tzw. klauzul Horna, czyli takich, które w następniku mają zero lub jeden term (n = 0 lub n = 1). I tak: Klauzule postaci (n = 1 i m > 0): A1 ∧ A2 ∧ ... ∧ Am ⇒ B1 nazywa się regułami. Klauzule postaci (n = 1 i m = 0): B1 nazywamy faktami. Klauzule postaci (n = 0): A1 ∧ A2 ∧ ... ∧ Am nazywamy celem, czyli tym co chcemy udowodnić. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 5 Rezolucja (Robinson, 1965) i Unifikacja Rezolucja to metoda wnioskowania do stosowanych tutaj stwierdzeń. Podstawowa jej reguła jest następująca: wiedząc że P ⇒ Q oraz R ⇒ S wnioskujemy że P ⇒ S o ile tylko Q i R dają się zunifikować, tj. znajdziemy takie wartości dla zmiennych od których zależą, dzięki którym uzyskamy równość: Q = R Z technicznego punktu widzenia, osiąga się to przez wyliczenie P ∧ R oraz Q ∧ S i usunięcie termów, które występują po obydwu stronach wyrażenia. Występowanie zmiennych w stwierdzeniach powoduje, że w trakcie rezolucji trzeba znaleźć takie wartości dla tych zmiennych, które pozwolą na odpowiednie dopasowanie. Podstawianie pod zmienne tymczasowych wartości pozwalających na unifikację zwane jest instancjonowaniem. Mówimy też o utożsamieniu zmiennej z wartością. Unifikacja często wymaga nawrotów, tj. zmienna jest instancjonowana, lecz dopasowanie nie udaje się i wówczas zmienną instancjonuje się inną wartością. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 6 Dowodzenie twierdzeń przez rezolucję W krokach stosowanie rezolucji wygląda następująco: Tworzymy zbiór stwierdzeń zawierający założenia twierdzenia (hipotezy) i negację tezy twierdzenia (cel). Hipotezy można uważać za bazę danych. Za pomocą rezolucji dochodzimy do sprzeczności. To dowodzi twierdzenia. Zasada jest prosta, ale złożoność czasowa może być barierą nie do pokonania... dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 7 PROLOG Prolog wywodzi się z prac badawczych prowadzonych w latach 70-tych XX wieku w Marsylii i w Edynburgu. Opracowany został z myślą o przetwarzaniu języka naturalnego i automatycznym dowodzeniu twierdzeń. W latach 80-tych stworzono też wersje na (ówczesne) mikrokomputery, np. bardzo udaną wersję micro-Prolog. Prolog to język programowania logicznego, lub dokładniej programowania w logice a jego nazwa pochodzi od: PROgrammation LOGique, PROgramming in LOGic. Obecnie dostępnych jest szereg implementacji języka Prolog, w tym swobodnie dostępne kompilatory Prologu, to: Jan Wielemaker, SWI-Prolog, http://www.swi-prolog.org Daniel Diaz, GNU-Prolog, http://gnu-prolog.inria.fr Na ćwiczeniach korzystać będziemy z dystrybucji SWI-Prolog. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 8 Termy w Prologu Term w Prologu to stała, zmienna lub struktura (termy złożone). Stała może być atomem (składnia - jak typowy identyfikator lub napis ujęty w apostrofy) lub liczbą całkowitą. Nazwy zmiennych zaczynają się od dużej litery. Instancjonowanie zmiennych następuje wyłącznie w trakcie rezolucji. Trwa tylko do czasu osiągnięcia celu. Struktura ma postać funktor(listaParametrów) gdzie parametry są atomami, zmiennymi lub innymi strukturami. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 9 Stwierdzenia w Prologu Stwierdzenia występują w dwóch formach, odpowiadających klauzulom Horna z głową. Pojedyncza struktura z kropką na końcu, to fakt (tzn. zakładamy, że stwierdzenie jest prawdziwe), np. ojciec(jan, janusz). Stwierdzenie zawierające następnik będący pojedynczą strukturą i poprzednik będący strukturą lub koniunkcją kilku struktur, to reguła; zapisujemy ją z kropką na końcu, a następnik od poprzednika oddzielamy znakiem ”:-” (traktowanym jako implikacja), np. dziadek(X, Z) :- ojciec(X, Y), ojciec(Y, Z). Można przyjąć, że przed każdą klauzulą stoi niejawny kwantyfikator uniwersalny, wiążący zmienne tej klauzuli. Program w języku Prolog, to nic innego, jak zbiór stwierdzeń takich, jak opisano powyżej. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 10 Cel w Prologu Stwierdzenie, które chcemy udowodnić (lub obalić, tzn. udowodnić jego fałszywość), nazywamy celem. Formalnie jest to klauzula bez głowy; składniowo wygląda tak samo, jak fakt. Rozróżnienie pomiędzy celem a faktem (będącym częścią programu) bierze się z trybu wpisywania w interpreterze. Po wpisaniu celu interpreter Prologu odpowie true lub false. Odpowiedź true oznacza, że udało się udowodnić cel przy podanych w programie założeniach. Odpowiedź false oznacza, że udało się obalić cel lub że nie udało się go udowodnić przy podanych założeniach. Jeśli cel jest stwierdzeniem złożonym, każda z zawartych w nim struktur zwana jest podcelem. Cel może zawierać zmienne. W takim przypadku Prolog znajdzie instancje, dla których cel jest prawdziwy. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 11 Rezolucja w Prologu Prolog stosuje metodę zstępującą, tzn. zaczyna od celu i próbuje znaleźć ciąg pasujących stwierdzeń, które prowadzą do pewnego zbioru faktów w programie. Ów ciąg stanowi dowód celu. Zauważmy, że można by stosować metodą wstępującą, tzn. zacząć od faktów w programie i próbować dojść do celu. Metoda wstępująca byłaby dobra przy dużym zbiorze rozwiązań; przy małym lepsza jest metoda zstępująca. Dla stwierdzeń złożonych używane jest przeszukiwanie w głąb, tzn. najpierw znajduje się dowód dla pierwszego podcelu, a potem przetwarza się następne podcele. Jeśli Prologowi nie uda się udowodnić jednego z podcelów, porzuca ów podcel i wraca do poprzednich podcelów, próbując znaleźć alternatywne rozwiązania. Proces ten zwany jest nawracaniem. Przeszukiwanie bazy danych postępuje zawsze od pierwszego stwierdzenia do ostatniego. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 12 Arytmetyka w Prologu W oryginalnym Prologu dostępna jest tylko arytmetyka na liczbach całkowitych. W wersji SWI-Prolog mamy możliwość pracy na liczbach zmiennoprzecinkowych. Dla uproszczenia zapisu wprowadzono infiksowe operatory arytmetyczne, np.: +,-,*,/,// i operator is. Stosuje się go do zunifikowania zmiennej (która musi być nie zainstancjonowana) z wartością wyrażenia arytmetycznego. Przykład: X is 2 * Y + 3 dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 13 Przykład arytmetyki w Prologu Powiedzmy, że dysponujemy bazą która zawiera informacje o: pokonanym dystansie (np. w metrach) przez chłopców: janek, michał i tomek: dystans(janek, 9600). dystans(michał, 8100). dystans(tomek, 13500). oraz czasie (np. w minutach) pokonania tych dystansów: czas(janek, 120). czas(michał, 90). czas(tomek, 135). Dołączamy teraz do bazy funktor prędkość(X, Y) który pozwala obliczyć średnią prędkość osoby X (lub sprawdzić, że wynosi ona Y). prędkość(X, Y) :- czas(X, C), dystans(X, D), Y is D/C. Umieszczenie struktury Y is D/C jako ostatniej jest istotne; dzięki temu Prolog najpierw instancjonuje zmienne D i C przez dopasowanie do faktów, a potem wylicza (lub sprawdza) wartość Y. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 14 Przykład arytmetyki w Prologu Uruchomienie przykładu z poprzedniego slajdu umożliwi teraz zadawanie zapytań: dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 15 Listy w Prologu Listy w Prologu to dowolnej długości ciągi elementów (atomów, termów, a także innych list). Składnia podobna jest do Haskella: nawiasy kwadratowe, elementy rozdzielone przecinkami. Nie ma jawnych funkcji do tworzenia i rozkładania list. Z uwagi na dopasowywanie, wystarcza odpowiednie użycie zapisu [X | Y] gdzie X jest głową, a Y ogonem listy. Np. X=[a,b,c]. Y=[2,4,6,ala]. Z=[]. X=[”ala”,123]. [X|Y]=[1,2,3]. [X,Y|Z]=[3,4,”ala”]. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 16 Listy - wywołanie przykładu Wywołanie z poprzedniego slajdu da następujące rezultaty: dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 17 Listy - kilka przykładów predykatów Obliczanie długości listy: dlugosc([],0). dlugosc([_|Ogon],Dlug) :- dlugosc(Ogon,X), Dlug is X+1. Sprawdzenie czy jakiś element należy do listy: czyNalezy(X,[X,_]). czyNalezy(X,[_,Y]) :- czyNalezy(X,Y). Sklejanie dwóch list: sklej([],X,X). sklej([X|L1],L2,[X|L3]) :- sklej(L1,L2,L3). Tutaj znak _ oznacza zmienną anonimową, która może być dopasowana do czegokolwiek. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 18 Listy - wywołanie przykładów dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 19 Interpreter - jak to właściwie działa Przypuśćmy, że dysponujemy następującą bazą wiedzy: lubi(jan, tatry). lubi(jan, beskidy). lubi(jerzy, beskidy). lubi(jerzy, bieszczady). lubiToSamo(X, Y) :- lubi(X, S), lubi(Y, S), X \= Y. Co się dzieje, gdy zadamy pytanie lubiToSamo(jan, X)? Następuje utożsamienie zmiennej X w definicji lubiToSamo(...) z atomem jan; sytuacja jest zatem taka, jakbyśmy mieli; lubiToSamo(jan, Y) :- lubi(jan, S), lubi(Y, S), jan \= Y. Interpreter szuka teraz dowodu dla pierwszego podcelu, czyli lubi(jan, S). Pierwsza możliwość to utożsamienie S z atomem tatry. Interpreter przechodzi do dowodu drugiego podcelu, czyli teraz lubi(Y, tatry). Jedyna możliwość to utożsamienie Y z atomem jan. W tej sytuacji trzeci podcel (X \= Y) nie daje się udowodnić. Potrzebny jest nawrót. Nawrót do drugiego podcelu nic nie daje - nie ma alternatywnego instancjonowania dla Y, a S zostało zainstancjonowane wcześniej. Następuje zatem nawrót do pierwszego podcelu i alternatywne instancjonowanie zmiennej S na atom beskidy. W kolejnym kroku mamy ponownie utożsamienie Y z atomem jan i ponownie nawrót przy X \= Y. Tym razem jednak wystarcza nawrót do drugiego podcelu i alternatywne instancjonowanie Y na jerzy. Trzeci podcel jest teraz spełniony (X \= Y) i mamy: X = jan, Y = jerzy, S = beskidy i odpowiedź interpretera X = jerzy. dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 20 Dziękuję za Uwagę!!! ☺ dr Robert Kowalczyk, Katedra Analizy Nieliniowej, WMiI UŁ 21