Języki i paradygmaty programowania

Podobne dokumenty
Przykład: Σ = {0, 1} Σ - zbiór wszystkich skończonych ciagów binarnych. L 1 = {0, 00, 000,...,1, 11, 111,... } L 2 = {01, 1010, 001, 11}

Matematyczne Podstawy Informatyki

1 Automaty niedeterministyczne

Języki formalne i automaty Ćwiczenia 7

Matematyczne Podstawy Informatyki

Wyrażenia regularne.

Języki formalne i automaty Ćwiczenia 6

Teoretyczne podstawy informatyki

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Języki i paradygmaty programowania. I. Wprowadzenie

Programowanie w języku C++ Podstawowe paradygmaty programowania

Paradygmaty programowania

Jaki język zrozumie automat?

Programowanie obiektowo zorientowane. Mirosław Głowacki Wykład w języku C++

Maszyna Turinga języki

Programowanie obiektowe - 1.

Języki programowania zasady ich tworzenia

Obliczenia inspirowane Naturą

Języki programowania deklaratywnego

KATEDRA INFORMATYKI TECHNICZNEJ. Ćwiczenia laboratoryjne z Logiki Układów Cyfrowych. ćwiczenie 204

Zadanie 1. Czy prawdziwa jest następująca implikacja? Jeśli L A jest językiem regularnym, to regularnym językiem jest też. A = (A, Q, q I, F, δ)

Hierarchia Chomsky ego Maszyna Turinga

Wprowadzenie: języki, symbole, alfabety, łańcuchy Języki formalne i automaty. Literatura

Rozwiązania około dwustu łatwych zadań z języków formalnych i złożoności obliczeniowej i być może jednego chyba trudnego (w trakcie tworzenia)

Wstęp do Programowania Obiektowego. Wykład 13 Paradygmaty. Składnia i semantyka.

Imię, nazwisko, nr indeksu

Języki, automaty i obliczenia

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

Programowanie komputerów

AUTOMATY SKOŃCZONE. Automat skończony przedstawiamy formalnie jako uporządkowaną piątkę:

1. Synteza automatów Moore a i Mealy realizujących zadane przekształcenie 2. Transformacja automatu Moore a w automat Mealy i odwrotnie

Analiza leksykalna 1. Teoria kompilacji. Dr inż. Janusz Majewski Katedra Informatyki

Algorytm. a programowanie -

Języki formalne i automaty Ćwiczenia 1

Języki formalne i automaty Ćwiczenia 9

Programowanie. programowania. Klasa 3 Lekcja 9 PASCAL & C++

INFORMATYKA, TECHNOLOGIA INFORMACYJNA ORAZ INFORMATYKA W LOGISTYCE

LOGIKA I TEORIA ZBIORÓW

Matematyczna wieża Babel. 4. Ograniczone maszyny Turinga o językach kontekstowych materiały do ćwiczeń

Automat ze stosem. Języki formalne i automaty. Dr inż. Janusz Majewski Katedra Informatyki

Paradygmaty i języki programowania. Analiza leksykalna Skaner, RE, DAS, NAS, ε- NAS

Definicje. Algorytm to:

Matematyczne Podstawy Informatyki

Języki programowania deklaratywnego

Wykład 0 Informacje Podstawowe

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Kurs programowania. Wstęp - wykład 0. Wojciech Macyna. 22 lutego 2016

Klasy abstrakcyjne i interfejsy

Zadanie analizy leksykalnej

Paradygmaty programowania

Języki regularne, rozpoznawanie wzorców regularnych, automaty skończone, wyrażenia regularne

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Zadanie 1. (6 punktów) Słowo w nazwiemy anagramem słowa v jeśli w można otrzymać z v poprzez zamianę kolejności liter. Niech

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

10110 =

Obliczenia inspirowane Naturą

JAO - Języki, Automaty i Obliczenia - Wykład 2. JAO - Języki, Automaty i Obliczenia - Wykład 2

Klasyczne i kwantowe podejście do teorii automatów i języków formalnych p.1/33

Temat: Zastosowanie wyrażeń regularnych do syntezy i analizy automatów skończonych

L E X. Generator analizatorów leksykalnych

Hierarchia Chomsky ego

Myśl w języku Python! : nauka programowania / Allen B. Downey. Gliwice, cop Spis treści

Dopełnienie to można wyrazić w następujący sposób:

Maszyna Turinga. Algorytm. czy program???? Problem Hilberta: Przykłady algorytmów. Cechy algorytmu: Pojęcie algorytmu

Wyrażenie nawiasowe. Wyrażenie puste jest poprawnym wyrażeniem nawiasowym.

złożony ze słów zerojedynkowych o długości co najmniej 3, w których druga i trzecia litera od końca sa

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

Analiza leksykalna 1. Języki formalne i automaty. Dr inż. Janusz Majewski Katedra Informatyki

1. Nagłówek funkcji: int funkcja(void); wskazuje na to, że ta funkcja. 2. Schemat blokowy przedstawia algorytm obliczania

Python wprowadzenie. Warszawa, 24 marca PROGRAMOWANIE I SZKOLENIA

(j, k) jeśli k j w przeciwnym przypadku.

Definiowanie języka przez wyrażenie regularne(wr)

Programowanie obiektowe

Typy klasowe (klasy) 1. Programowanie obiektowe. 2. Założenia paradygmatu obiektowego:

Podstawy Programowania Obiektowego

Wykład V. Rzut okiem na języki programowania. Studia Podyplomowe INFORMATYKA Podstawy Informatyki

Programowanie w języku Python. Grażyna Koba

Wykład z Technologii Informacyjnych. Piotr Mika

Zaawansowane programowanie obiektowe - wykład 5

Technologie informacyjne - wykład 12 -

6. Pętle while. Przykłady

Algorytmy sztucznej inteligencji

Język ludzki kod maszynowy

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

Modele Obliczeń. Wykład 1 - Wprowadzenie. Marcin Szczuka. Instytut Matematyki, Uniwersytet Warszawski

Programowanie strukturalne. Opis ogólny programu w Turbo Pascalu

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

KONSTRUKCJA KOMPILATORÓW

5. Algebra działania, grupy, grupy permutacji, pierścienie, ciała, pierścień wielomianów.

2.2. Gramatyki, wyprowadzenia, hierarchia Chomsky'ego

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Podstawy programowania funkcjonalnego

Wykład 8: klasy cz. 4

Laboratorium 10: Maszyna stanów

Algorytm. Krótka historia algorytmów

Technologie i usługi internetowe cz. 2

Symbol, alfabet, łańcuch

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Gramatyki, wyprowadzenia, hierarchia Chomsky ego. Gramatyka

Transkrypt:

Języki i paradygmaty programowania semestr letni 2016/2017 wersja z dnia: 6 czerwca 2017 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 1 / 289

Literatura podstawowa Aut J.E. Hopcroft, R. Motwani, J.D. Ullman, Wprowadzenie do teorii automatów, języków i obliczeń, wyd. 2, PWN, W-wa 2012 7J B.A. Tate, Siedem języków w siedem tygodni. Praktyczny przewodnik nauki języków programowania, Helion 2011 Literatura dodatkowa J. Bylina, B. Bylina, Przegląd języków i paradygmatów programowania, skrypt UMCS, Lublin 2011 M. Lipovača, Learn You a Haskell for Great Good! A Beginner s Guide, No Starch Press 2011 (dostępny również on-line: learnyouahaskell.com) W.F. Clocksin, C.S. Mellish, Prolog. Programowanie, Helion (druk na żądanie) dokumentacja i samouczki do języków: ruby, haskell i prolog Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 2 / 289

Spotykamy się w czwartki 08:30 10:00, Duża Aula Inf. (105). Warunki zaliczenia: zaliczone ćwiczenia zdany egzamin 5 ECTS = 125-150 godzin pracy czyli po 7-10 godzin pracy tygodniowo! Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 3 / 289

Dlaczego a nie Języki i paradygmaty programowania, Paradygmaty i języki programowania? O jakie języki chodzi? Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 4 / 289

Proces tworzenia programu: 1 cel co program ma robić 2 projekt dobór algorytmu 3 opis dobór głównego paradygmatu 4 technika dobór technik programowania 5 narzędzia wybór języka programowania 6 wykonanie napisanie kodu, sprawdzenie, uruchomienie Punkty 1 5 to praca koncepcyjna, dopiero ostatni punkt to klepanie w klawiaturę. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 5 / 289

Paradygmat określa ogólny sposób podejścia do opisu problemu. Jeśli znam algorytm, który jest liniowy (ew. drzewiasty) i niezbyt złożony, to wybieram czysty paradygmat imperatywny algorytm zawiera wiele powtarzających się fragmentów, wygodnie jest sięgnąć po paradygmat strukturalny/proceduralny algorytm zawiera klasy abstrakcji, klasyfikujące dane po ich właściwościach paradygmat obiektowy nie wiem jak, ale wiem z czym startuję, co mogę zrobić i co chcę uzyskać na końcu paradygmat deklaratywny j.w. i mogę wszystko sprowadzić do matematycznego opisu paradygmat deklaratywny funkcyjny j.w. i mogę wszystko sprowadzić do wyrażeń Boole a paradygmat deklaratywny logiczny Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 6 / 289

Nie jest prawdą, że każdy język programowania jest tak samo dobry do realizacji danego zadania, co inny. Istnieją wyspecjalizowane języki ułatwiające rozwiązywanie konkretnych typów problemów. Wybór odpowiedniego narzędzia jest ważny! Przykład: wyświetl drugie i trzecie pole każdej linii (pola oddzielane są dwukropkiem) pliku tekstowego 1 awk BEGIN {FS =":"};{ print $2,$3} plik. txt A teraz proszę napisać to samo w C. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 7 / 289

Skoro konstrukcja języka programowania podlega paradygmatowi, dlaczego Języki i paradygmaty programowania, a nie Paradygmaty i języki programowania? Ponieważ zajmować się będziemy głównie abstrakcyjnymi strukturami i właściwościami języków formalnych opisujących automaty, wyrażenia regularne i gramatyki. Te struktury pozwalają na stworzenie spójnego języka programowania, ale mogą służyć również do opisu języków naturalnych i innych. To nie jest kolejny wykład z języka programowania! Chociaż dwa nowe języki się pojawią... Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 8 / 289

Plan wykładów Maszyny programowalne, języki programowania Podstawowe paradygmaty programowania: imperatywne, imperatywne obiektowe, deklaratywne funkcyjne, deklaratywne w logice Automaty, alfabety, języki Aut[1.5] Automaty deterministyczne Aut[2.2] Automaty niedeterministyczne Aut[2.3],[2.5] Wyrażenia regularne Aut[3.1] [3.4] Gramatyki bezkontekstowe Aut[5.1],[5.2] Analiza leksykalna i syntaktyczna kodu Aut[5.3] i inne Wieloznaczność gramatyk Aut[5.4] Automaty ze stosem Aut[6.1] [6.4] Zmienne i typy danych Programowanie funkcyjne: rachunek lambda Programowanie funkcyjne: monady 7J[8.4] Programowanie funkcyjne: haskell 7J[8.1] [8.5] i inne Programowanie w logice: prolog 7J[4.1] [4.5] i inne Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 9 / 289

Przegląd paradygmatów programowania Programowanie maszyn sprzęt musi być programowalny nie każdy układ elektroniczny to procesor sam procesor to jeszcze nie komputer zazwyczaj nie programujemy elektroniki bezpośrednio warstwy abstrakcji oddzielające użytkownika od sprzętu systemy wbudowane (np. kontrolery przemysłowe, telefony komórkowe) systemy operacyjne (np. bankomaty, komputery) do wydawania instrukcji używany jest język programowania (oparty o jakiś paradygmat) język maszynowy asembler języki wyższego poziomu kompilator interpreter maszyna wirtualna Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 10 / 289

Przegląd paradygmatów programowania PAMIĘĆ Architektura Princeton (koncepcja von Neumanna) IN OUT AKUMU- LATOR CPU ALU JEDNOSTKA STERUJĄCA pamięć przechowuje zarówno dane jak i instrukcje CPU nie może jednocześnie pobrać instrukcji i wykonać operacji na danych Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 11 / 289

Przegląd paradygmatów programowania PAMIĘĆ DANE PAMIĘĆ Architektura Harward IN OUT AKUMU- LATOR CPU ALU JEDNOSTKA STERUJĄCA PAMIĘĆ PROGRAM pamięć danych i pamięć rozkazów są rozdzielone CPU może jednocześnie dokonywać odczytu/zapisu do pamięci i wykonywać instrukcje programu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 12 / 289

Przegląd paradygmatów programowania Tradycyjnie mówi się, że język programowania musi dać możliwość operowania na zmiennych operowania na podstawowych strukturach danych wykonania instrukcji warunkowej wykonania pętli lub ew. zdefiniowania struktur rekurencyjnych tak, aby dało się w nim zapisać dowolny algorytm (język uniwersalny). Nie każdy język umożliwia programowanie: języki znacznikowe (np. HTML) część języków skryptowych większość makrojęzyków programowalne kalkulatory Nie każde podanie ciągu instrukcji jest programowaniem. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 13 / 289

Przegląd paradygmatów programowania Paradygmat «przyjęty sposób widzenia rzeczywistości w danej dziedzinie, doktrynie» [sjp.pwn.pl] Paradygmaty programowania paradygmat jest to wzorzec, teoria i metodologia czegoś coś narzucone, dane odgórnie na początku paradygmat programowania był determinowany przez architekturę maszyny obecnie ten problem został zepchnięty na poziom kompilatora często języki są uniwersalne, kod jest przenośny pomiędzy maszynami dobry przykład: jądro linuksa (napisane w C, można skompilować na kilkunastu różnych architekturach) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 14 / 289

Przegląd paradygmatów programowania Podstawowy podział paradygmatów Imperatywny ściśle oparty na architekturze von Neumanna zmiana stanu programu wraz z kolejnymi krokami czasowymi struktury kontrolne programu określają kolejność wywołań instrukcji programy przypominają instrukcje obsługi, przepisy kuchenne etc., czyli musimy znać metodę osiągnięcia celu (algorytm prowadzący od danych do wyniku) Deklaratywny wzorowany na językach naturalnych i ludzkich procesach myślowych nieliniowość wykonania, komenda na końcu kodu może zmodyfikować wywołanie z jego początku powszechne stosowanie rekurencji w miejsce pętli definiuje problem (dane i reguły) i poszukuje odpowiedzi na zadane pytanie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 15 / 289

Przegląd paradygmatów programowania Dalszy podział paradygmatu imperatywnego strukturalny bazuje na strukturze kodu (bloki), pozbawionego instrukcji skoku goto; kod jest czystszy i łatwiejszy do analizy, program, za wyjątkiem pętli, wykonywany jest po kolei proceduralny odmiana strukturalnego, w której część kodu jest zestawiana w podprogramy: procedury, funkcje; podprogramy mogą byś wywoływane wielokrotnie z głównego programu lub z innych podprogramów obiektowy definiuje się metody działania na klasach danych, nie na pojedynczych instancjach; przynależność do klasy, a więc właściwości, obiekt dziedziczy hierarchicznie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 16 / 289

Przegląd paradygmatów programowania Dwa główne nurty programowania deklaratywnego funkcyjne zamiast instrukcji używa się wyrażeń, a więc nie ma pojęcia stanu programu; cały algorytm sprowadza się do obliczenia wartości pewnej (zazwyczaj złożonej) funkcji matematycznej; u podstaw programowania funkcyjnego leży rachunek lambda w logice zamiast instrukcji używa się zdań logicznych, a więc każde działanie sprowadza się do określenia, czy rozważane stwierdzenie jest logicznie prawdziwe, czy fałszywe Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 17 / 289

Przegląd paradygmatów programowania Obecnie wyróżnia się cztery główne paradygmaty, często traktowane jako niezależne: imperatywny obiektowy funkcyjny w logice Większość języków (niemal wszystkie nowoczesne) jest hybrydowa i nie podlega dokładnie pod tylko jeden paradygmat. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 18 / 289

Przegląd paradygmatów programowania Program w języku imperatywnym ściśle powiązany z koncepcją maszyny von Neumanna polega na wydaniu sekwencji uszeregowanych czasowo rozkazów, zmieniających stan maszyny stan maszyny oznacza stan jej procesora i zawartość pamięci oraz rejestrów języki niskiego poziomu (asemblery) operują niemal bezpośrednio na jednostkach pamięci języki wysokiego poziomu wprowadzają twory abstrakcyjne reprezentujące składowe maszyny, np. zmienne reprezentują jednostki pamięci Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 19 / 289

Przegląd paradygmatów programowania Przykład: kod w Pascalu 1 program silnia ; 2 var i, n, s: integer ; 3 begin 4 read (n); 5 s := 1; 6 for i := 2 to n do 7 s := s * i; 8 write (s) 9 end. Przykład: kod w Fortranie 1 program silnia 2 integer i, n, s 3 read (*,*) n 4 s=1 5 do 10 i=2,n 6 s = s * i 7 10 continue 8 write (*,*) s 9 end program Języki czysto imperatywne: C, Pascal, Fortran, Basic, asemblery... Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 20 / 289

Przegląd paradygmatów programowania Program w języku obiektowym przede wszystkim jest to rodzina imperatywna, więc struktura i logika kodu jest taka sama zmiana następuje na poziomie struktury danych: dane grupowane są z operacjami, które można na nich wykonać, w nowy twór obiekt hermetyzacja (enkapsulacja) polega na ukryciu implementacji obiektu, zaś udostępnieniu jedynie niektórych danych i metod w postaci interfejsu obiekty można grupować w hierarchicznie definiowane klasy, które mają pewne wspólne cechy (mechanizm dziedziczenia w ramach hierarchii) hierarchia klas implikuje zawieranie się, co prowadzi do polimorfizmu dynamicznego metody dobierane są automatycznie zgodnie z przynależnością do klasy abstrakcja danych pozwala na definiowanie klas w postaci ogólnych szablonów, a na ich podstawie dopiero tworzenia szczegółowszych definicji Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 21 / 289

Przegląd paradygmatów programowania Główne pojęcia związane z paradygmatem obiektowym: Klasa to abstrakcyjny typ danych, definiowany programowo poprzez podanie dopuszczalnych na nim operacji oraz jego reprezentację. Obiekt to element należący do pewnej klasy. Klasa potomna (pochodna, podklasa) jest klasą wywiedzioną poprzez dziedziczenie z innej klasy. Klasa macierzysta (nadrzędna, nadklasa) jest klasą, z której wywodzi się klasa potomna. Metoda to podprogram (operacja) działający na obiektach danej klasy zgodnie z jej i jego definicją. Pole to zmienna zamknięta wewnątz obiektu. Komunikat to wywołanie metody. Protokół obiektu to zbiór sposobów odwołania się do obiektu. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 22 / 289

Przegląd paradygmatów programowania To, co jest w ramach danego obiektu ukryte, a co dostępne, określają kwalifikatory przy metodach i polach: public oznacza nieograniczoną dostępność z zewnątrz private oznacza ukrycie przed dostępem z zewnątrz i pozwala przez to na definiowanie typów abstrakcyjnych protected oznacza ukrycie dla wszystkich za wyjątkiem klas potomnych W wielu językach kwalifikatory przyjmują domyślne stałe wartości i jawnie się ich nie używa. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 23 / 289

Przegląd paradygmatów programowania Przykład: kod w C++ 1 unsigned int factorial ( unsigned int x) 2 { 3 unsigned int value = 1; 4 for ( unsigned int i = 2; i <= x; i ++) 5 { 6 value = value * i; 7 } 8 return value ; 9 } Jeśli nie wykorzystuje się obiektów, język obiektowy jest nie do odróżnienia od czystego języka imperatywnego. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 24 / 289

Przegląd paradygmatów programowania 7J [2.4] Przykład: kod w Ruby 1 class Song 2 def initialize ( name, artist, duration ) 3 @ name = name 4 @artist = artist 5 @duration = duration 6 end 7 end 8 9 asong = Song. new (" Bicylops ", " Fleck ", 260) 10 asong. inspect Języki obiektowe: C++, C#, Delphi / Object Pascal, Java, Ada, Python, Ruby... Jest to dominujący obecnie paradygmat. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 25 / 289

Przegląd paradygmatów programowania 7J [2.4] Ruby jest językiem niezwykle elastycznym, do tego stopnia, że struktura klas nie jest sztywna. Mówi się, że Ruby posiada otwarte klasy. definicje klas i metod są dostępne dla programisty można je (niemal) dowolnie modyfikować...... ponosząc konsekwencje naruszenia struktury Ingerencja w klasy i metody jest czasami bardzo wygodna, ale programista musi pamiętać, że z powodu dziedziczenia, zmiany w klasie nadrzędnej będą się propagowały do klas potomnych. Może to prowadzić do trudnych do wykrycia błędów. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 26 / 289

Przegląd paradygmatów programowania 7J [2.4] Przykład: przedefiniowanie operatora + w obrębie klasy Fixnum piotrsarnacki.com/2008/01/08/otwarte-klasy-w-rubim-i-ich-praktyczne-wykorzystanie/ Plik klasa.rb 1 # -*- coding : iso -8859-2 -*- 2 class Fixnum 3 alias old_plus + 4 5 def +( wyrażenie ) 6 ( self. old_plus wyrażenie ) % 7 7 end 8 end 9 10 a = 4 + 4 11 puts a Uruchomienie programu 1 [ bash ]$ ruby klasa.rb 2 1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 27 / 289

Przegląd paradygmatów programowania 7J [4.x] [8.x] Programowanie deklaratywne (funkcyjne i logiczne) będzie omawiane dokładniej w dalszej części wykładu. Na razie przykłady: Definicja funkcji silnia w Haskellu 1 silnia :: Integer - > Integer 2 silnia 0 = 1 3 silnia x = x * silnia ( x -1) Definicja predykatu silnia w Prologu 1 silnia (0,X) :- X=1,!. 2 silnia (N,X) :- N >0, L is N -1, 3 silnia (L,Y), X is Y*N. W językach deklaratywnych bardzo często efektywniejsze są algorytmy rekurencyjne, niż te oparte o pętle. Niekiedy pętli nie da się w ogóle zdefiniować ze względu na brak możliwości użycia zmiennych sterujących przebiegiem pętli. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 28 / 289

Przegląd paradygmatów programowania 7J [4.x] [8.x] UWAGA Jak na razie wszystkie komputery ) oparte są na imperatywnej architekturze von Neumanna. Dlatego też, niezależnie od użytego języka programowania, kompilator i tak tłumaczy każdy kod na zestaw uszeregowanych czasowo instrukcji zmieniających stan maszyny. ) Wyjątkiem są komputery kwantowe, które są oparte na zupełnie innej zasadzie działania zarówno pod względem budowy, jak i programowania i realizacji algorytmów. W ich przypadku jednak póki co nie istnieją języki programowania, podobnie jak nie istnieją jeszcze uniwersalne programowalne komputery kwantowe. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 29 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Automaty Automat to abstrakcyjny model opisu pewnej klasy maszyn obliczeniowych i oprogramowania. Jest użyteczny w przypadku procesów, w których można wyodrębnić zbiór parametrów w całości je definiujący. W przypadku oprogramowania może symulować instrukcje warunkowe, skoki i pętle/rekurencje. Automat składa się ze zbioru stanów i reguł przejścia między nimi: stan jest definiowany wartościami parametrów opisujących cały układ; można go porównać z położeniem zatrzymanego układu trybów w mechanizmie, stanu napięcia sprężyn, stanu wahadła itd. reguły przejścia określają, jak zmienią się parametry aby dokonało się przejście z pewnego stanu do innego (nie wszystkie przejścia muszą być dozwolone) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 30 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Automat skończony zawiera skończoną liczbę stanów, w odróżnieniu od automatów nieskończonych, które są nieograniczone. Tymi drugimi nie będziemy się zajmować. Przykład: w(y)łącznik światła naciśnij start zapalone zgaszone naciśnij zapalone i zgaszone to dwa możliwe stany przełącznika przejście między tymi stanami następuje poprzez zaistnienie sytuacji naciśnij ta sytuacja jest odczytywana z wejścia automatu (nie zaznaczonego na rysunku), czyli automat odczytuje, analizuje i reaguje na dane wejściowe Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 31 / 289

Podstawowe definicje Podstawowe pojęcia teorii automatów Aut [1.5] Alfabet Σ to skończony niepusty zbiór symboli. Słowo (łańcuch) to ciąg symboli wybranych z pewnego alfabetu. Specjalnym słowem jest łańcuch pusty ε, który należy do każdego alfabetu. Język L to zbiór wszystkich słów podlegających pewnej regule, jakie można ułożyć z danego alfabetu. Przykładowe alfabety Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} Σ = {0, 1} Σ = {a, b,..., z} Σ = ASCII dziesiętny binarny małe litery znaki ASCII Przykładowe słowa w alfabecie binarnym Σ = {0, 1}: ε, 0, 1, 00, 01, 10, 11, 000, 001, 010, 011, 100, 101, 110, 111,... Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 32 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Wprowadza się potęgi alfabetu, oznaczające wybór słów pewnej długości: Σ 0 = {ε} Σ 1 = {w : w = 1} = Σ Σ 2 = {w : w = 2} = ΣΣ Σ + = Σ 1 Σ 2... Σ = Σ i = Σ 0 Σ 1 Σ 2 = {ε} Σ + i=0 Symbol Σ w praktyce oznacza zbiór wszystkich słów (również słowa pustego) nad alfabetem Σ. Określenie słowo nad alfabetem Σ oznacza łańcuch należący do Σ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 33 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Stwierdzenie Konkatenacja ab dwóch słów a i b nad alfabetem Σ jest słowem nad alfabetem Σ. Jeśli a Σ n i b Σ m, to ab Σ n+m. Elementem neutralnym konkatenacji jest ε. Stwierdzenie Konkatenacja ab dwóch słów a Σ n a i b Σ m b Σ ab = (Σ a Σ b ). jest słowem nad alfabetem Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 34 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Definicja: język nad alfabetem Językiem L nad alfabetem Σ nazywamy każde L Σ. Język nie musi wykorzystywać wszystkich symboli alfabetu, dlatego L pozostaje językiem nad każdym alfabetem zawierającym w sobie Σ. Zbiór pusty L = jest językiem nad dowolnym alfabetem. L = {ε} również jest językiem nad dowolnym alfabetem. Język nie musi być skończony. Różnica między ε, {ε} a ε jest łańcuchem pustym, zawierającym zero symboli {ε} jest zbiorem jednoelementowym, zawierającym pusty łańcuch jest zbiorem pustym, zeroelementowym Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 35 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Przykład 1: Alfabetem jest alfabet łaciński z polskimi znakami diakrytycznymi. Językiem jest język polski. Reguła: Każde poprawne po polsku słowo będzie należało do omawianego języka. Reguła przynależności do języka determinowana występowaniem słowa w słowniku. Przykład 2: Alfabetem jest zbiór cyfr arabskich. Językiem jest zbiór liczb parzystych. Reguła: Każdy ciąg cyfr kończący się symbolem 0,2,4,6,8 należy do tego języka. Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} L = {xy : x Σ, y {0, 2, 4, 6, 8}} Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 36 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Przykład 3: Alfabetem jest zbiór cyfr arabskich. Językiem jest zbiór liczb naturalnych mniejszych od 1000. Reguła: Każdy ciąg cyfr o długości nie przekraczającej 3 należy do tego języka. Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} L = {w Σ : w 3} Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 37 / 289

Podstawowe pojęcia teorii automatów Aut [1.5] Przykład 4: Alfabetem jest alfabet łaciński wraz z cyframi i pewnymi znakami specjalnymi. Językiem jest zbiór poprawnych wyrażeń języka fortran. Reguła: musi istnieć słownik oraz pewne reguły konstruowania poprawnych słów (gramatyka) Problem: jak sprawdzić, czy pewne wyrażenie jest poprawnym wyrażeniem danego języka? Należy skonstruować automat rozpoznający poprawne wyrażenia danego języka. Znacznie prościej jest, jeśli język jest skończony. Dlatego też w językach programowania stosuje się reguły ograniczające programistę przy wyborze etykiet (nazw zmiennych, procedur...). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 38 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Automat deterministyczny to taki, w którym przejścia między stanami są dokładnie określone, tj. dla danego wejścia istnieje dokładnie jeden stan wyjściowy. Automat taki zawsze znajduje się w dobrze określonym stanie. Przykład: włącznik światła naciśnij start zapalone zgaszone naciśnij Automat niedeterministyczny to taki, w któym w danych warunkach istnieją przejścia do kilku stanów wyjściowych. Automat taki może znajdować się w kilku stanach jednocześnie. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 39 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Definicja: deterministyczny automat skończony Deterministyczny automat skończony (DAS) to automat skończony, deterministyczny, zdefiniowany poprzez (Q, Σ, δ, q 0, F ), gdzie Q to skończony zbiór stanów Σ to skończony zbiór symboli wejściowych (alfabet) δ to funkcje przejścia między stanami, δ : Q Σ Q q 0 Q to stan początkowy F Q to zbiór stanów akceptujących (stanów końcowych) Przykład: włącznik światła n start q0 n q1 Q = {q 0, q 1 } Σ = {n} δ(q 0, n) = q 1, δ(q 1, n) = q 0 F = {q 1 } Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 40 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Przykład: włącznik światła n start q0 n q1 n q 0 q 1 q 1 q 0 Tu oznacza stan wejściowy, zaś znakuje stany akceptowalne. Alfabet odczytać można z opisu pierwszego rzędu, stany z pierwszej kolumny, funkcje przejścia z treści tabelki. Mamy więc trzy równoważne zapisy definiujące DAS zapis piątkowy diagram tabela Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 41 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Przykład: Automat DAS A umcs rozpoznający w tekście ciąg UMCS. Alfabetem będzie alfabet polski + znaki interpunkcyjne Σ = {a,..., ż, A,..., Ż, 0,..., 9, znaki specjalne} A umcs najwygodniej zdefiniować diagramem: start U M C S q0 q1 q2 q3 q4 Σ \{U} Σ \{U,M} Σ \{U,C} Σ \{U,S} U U Gdyby DAS miał rozpoznawać wyraz UMCS to należałoby zacząć szukać dopasowania od znaku spacji, a zakończyć na znaku spacji lub znaku przestankowym. U Σ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 42 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Ten sam DAS określony za pomocą tabeli Definicje stanów: A umcs U M C S Σ\ {U,M,C,S} q 0 q 1 q 0 q 0 q 0 q 0 q 1 q 1 q 2 q 0 q 0 q 0 q 2 q 1 q 0 q 3 q 0 q 0 q 3 q 1 q 0 q 0 q 4 q 0 q 4 q 4 q 4 q 4 q 4 q 4 q 0 nie przeczytałem niczego interesującego q 1 przeczytałem U q 2 przeczytałem UM q 3 przeczytałem UMC q 4 przeczytałem UMCS Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 43 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Umowa Reguły rysowania diagramów (grafów) definiujących DAS: 1 każdemu stanowi z Q odpowiada jeden wierzchołek; wierzchołki są podpisane oznaczeniem stanu i rysujemy je w postaci kółka 2 na stan wejściowy wskazuje strzałka z napisem START 3 stany akceptujące oznaczane są podwójnym kółkiem 4 jeśli istnieje funkcja przejścia δ(q i, s) = q j, to stan q i łączymy strzałką ze stanem q j, a strzałkę podpisujemy symbolem s (ew. listą symboli, jeśli więcej niż jeden symbol powoduje takie przejście) 5 dla każdego stanu muszą istnieć funkcje δ od każdego symbolu używanego alfabetu 6 fragmenty diagramu, do których nie można się dostać ze stanu początkowego, są z niego usuwane Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 44 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Niekiedy wygodnie jest określić funkcję przejścia od łańcucha, a nie pojedynczego symbolu. Definicja: rozszerzona funkcja przejścia DAS Rozszerzona funkcja przejścia ˆδ zdefiniowana jest rekurencyjnie tak, że to jeśli m = lx jest łańcuchem składającym się z łańcucha l i symbolu x (l Σ, x Σ), oraz jeśli DAS będący w stanie q po przetworzeniu łańcucha l znajdzie się w stanie p, ˆδ(q, ε) = q ˆδ(q, m) = δ(p, x) = δ(ˆδ(q, l), x) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 45 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Przykładowo dla A umcs : ˆδ(q 0, UMCS) = q 4, ˆδ(q i, m) = q 0 dla i = 0, 1, 2, 3, m UMCS, ˆδ(q 4, m) = q 4 dla m dowolnego. UWAGA: Rozszerzona funkcja przejścia od łańcucha jednoelementowego redukuje się do normalnej funkcji przejścia ˆδ(q, x) = δ(q, x) Pamiętając o tym, można przyjąć to samo oznaczenie na obie funkcje. Dla klarowności nie będziemy tego tutaj robić. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 46 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.2] Rozszerzone funkcje przejścia mogą posłużyć do zdefiniowania języka DAS. Definicja: język DAS Językiem DAS L(A) nazywamy zbiór łańcuchów przeprowadzających stan początkowy automatu A w jeden z jego stanów akceptujących: L(A) = {w : ˆδ(q 0, w) F } Kontynuując poprzedni przykład, L(A umcs ) = {UMCS}. Definicja: język regularny Jeśli dla danego języka L istnieje taki DAS A, że L = L(A), to język L nazywany jest językiem regularnym. Innymi słowy, język jest regularny jeśli jest językiem jakiegoś DAS. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 47 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Definicja: niedeterministyczny automat skończony Niedeterministyczny automat skończony (NAS) to automat skończony, niedeterministyczny, zdefiniowany poprzez (Q, Σ, δ, q 0, F ), gdzie Q to skończony zbiór stanów Σ to skończony zbiór symboli wejściowych (alfabet) δ to funkcje przejścia między stanami, δ : Q Σ {Q } Q q 0 Q to stan początkowy F Q to zbiór stanów akceptujących (stanów końcowych) Jedyna różnica w definicjach DAS i NAS to postać funkcji przejścia δ: (DAS) δ : Q Σ Q (NAS) δ : Q Σ {Q } Q W przypadku NAS odwzorowanie δ mapuje stan i symbol na zbiór stanów, nie jest więc funkcją, chociaż takiej nazwy się używa. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 48 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Jak działają odwzorowania δ w przypadku NAS? automat z pojedynczego stanu przechodzi do jednego lub kilku stanów oznacza to, że może znajdować się w kilku stanach jednocześnie analizuje symultanicznie kilka możliwych ścieżek wędrówki po grafie; niektóre prowadzą do stanów akceptowalnych, inne się kończą na stanach nieakceptowalnych Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 49 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Przykład: NAS rozstrzygający, czy liczba jest parzysta. Dla ułatwienia, zapiszemy liczbę w postaci binarnej. Wtedy zadanie NAS sprowadza się do sprawdzenia, czy ciąg binarny kończy się symbolem 0. Diagram: start Tabelka: 0,1 q0 0 0 1 q1 q 0 {q 0, q 1 } {q 0 } q 1 Uwagi: z q 0 wychodzą dwie linie znakowane 0 i jedna 1 NAS kończy działanie wraz z końcem analizowanego łańcucha Pytanie: czy dałoby się skonstruować DAS wykonujący to samo zadanie? Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 50 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Definicja: rozszerzona funkcja przejścia NAS Rozszerzona funkcja przejścia ˆδ zdefiniowana jest rekurencyjnie tak, że to jeśli m = lx jest łańcuchem składającym się z łańcucha l i symbolu x (l Σ, x Σ), oraz jeśli NAS będący w stanie q po przetworzeniu łańcucha l znajdzie się w stanach {p 1,..., p k }, ˆδ(q, ε) = q k ˆδ(q, m) = δ(p i, x) i=1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 51 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Definicja: język NAS Językiem NAS L(A) nazywamy zbiór łańcuchów w przeprowadzających stan początkowy automatu A w zbiór stanów, zawierający co najmniej jeden stan akceptujący: L(A) = {w : ˆδ(q 0, w) F } w tym wypadku, w zbiorze wszystkich stanów pośrednich, jakie generuje łańcuch w, musi się znaleźć taki podzbiór, który utworzy w grafie ścieżkę łączącą stan początkowy q 0 z którymś ze stanów akceptujących fakt istnienia w zbiorze końcowym wielu stanów F nie gra roli fakt istnienia wielu ścieżek, które nie kończą się stanem akceptującym, również nie ma znaczenia Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 52 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Twierdzenie (nieformalne) o równoważności NAS i DAS Każdy język, który da się opisać za pomocą pewnego NAS, można opisać również za pomocą pewnego DAS. Automaty te są sobie równoważne. Uwagi: istnieją problemy, które łatwiej opisać DAS, a inne za pomocą NAS, ale jest to jedynie kwestia wygody i ew. elegancji zapisu równoważność działa oczywiście w obie strony DAS NAS z grubsza każdemu zbiorowi stanów NAS przypisuje się nowy stan DAS i na tej podstawie konstruuje funkcje przejścia δ jeśli NAS ma n stanów, to konstrukcja formalnie przewiduje 2 n stanów dla DAS w praktyce wiele z tych stanów jest nieosiągalnych (tworzą część grafu rozłączną ze stanem początkowym) i powinno się je usunąć, dlatego często liczba stanów NAS i DAS jest porównywalna Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 53 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Przykład: NAS rozpoznający liczby parzyste był zdefiniowany tabelką NAS 0 1 q 0 {q 0, q 1 } {q 0 } q 1 Można z niej wyodrębnić 4 różne stany:, q 0, q 1, {q 0, q 1 }. Pełna wersja tej tabelki przybiera postać (uwaga na 3. linię!): NAS 0 1 q 0 {q 0, q 1 } {q 0 } q 1 {q 0, q 1 } {q 0, q 1 } {q 0 } Widać, że zaczynając ze stanu q 0 nie osiągniemy stanu q 1 w postaci czystej, a jedynie pośrednio poprzez stan {q 0, q 1 }. Podobnie stan staje się niepotrzebny. Można więc usunąć linie drugą i czwartą z tabeli. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 54 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Po uporządkowaniu otrzymujemy przekształconą tabelę w postaci odpowiedniej dla DAS DAS 0 1 q 0 {q 0, q 1 } {q 0 } {q 0, q 1 } {q 0, q 1 } {q 0 } Lepiej to widać po zmianie nazw DAS 0 1 i narysowaniu diagramu 1 1 0 q 0 q 1 q 0 q 1 q 1 q 0 start q0 0 q1 Powyższa analiza jest przykładem tzw. konstrukcji podzbiorów. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 55 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Konstrukcja podzbiorów jest ogólną metodą przekształcenia NAS DAS polega na utworzeniu ze zbioru stanów NAS nowego stanu DAS na tej podstawie konstruuje się funkcje przejścia DAS operacja jest powtarzana do momentu, aż wszystkie stany zostaną opisane, a funkcja przejścia będzie przeprowadzała automat z dowolnego stanu w dokładnie jeden stan wyjściowy Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 56 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Twierdzenie o równoważności NAS i DAS Mając dany automat NAS N = (Q N, Σ, δ N, q 0, F N ) i skonstruowany na jego podstawie za pomocą konstrukcji podzbiorów automat DAS D = (Q D, Σ, δ D, {q 0 }, F D ), zachodzi równość języków L(D) = L(N). Aby udowodnić to twierdzenie należy pokazać, że z konstrukcji podzbiorów wynika równość ˆδ D (q 0, m) = ˆδ N (q 0, m) dla dowolnego słowa m. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 57 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Szkic dowodu: Niech m = lx, zaś stany p i są osiągane przez N ze stanu q 0 po przetworzeniu łańcucha l. Konstrukcja podzbiorów daje przepis: k δ D ({p 1,..., p k }, x) = δ N (p i, x). i=1 Automat DAS traktuje {p 1,..., p k } jako pojedynczy stan, więc zgodnie z definicją rozszerzonej funkcji przejścia dla DAS δ D ({p 1,..., p k }, x) = ˆδ D (q 0, m). Analogiczna definicja dla NAS mówi, że k δ N (p i, x) = ˆδ N (q 0, m). i=1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 58 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Ostatecznie więc otrzymujemy żądaną równość ˆδ D (q 0, m) = ˆδ N (q 0, m). Automat N akceptuje m jeśli zbiór ˆδ N (q 0, m) zawiera stan z F N. Automat D akceptuje m jeśli stan ˆδ D (q 0, m) zawiera stan z F D. Na podstawie udowodnionej równości mamy F N = F D. Wniosek: języki opisujące oba automaty są identyczne, L(D) = L(N). UWAGA: Pełny dowód patrz Aut, rozdz. 2.3.5. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 59 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Jak na razie twierdzenie mówi tylko, że z każdego NAS można zbudować (metodą konstrukcji podzbiorów) taki DAS, że języki akceptowane przez oba automaty są identyczne. Obserwacja: DAS jest szczególną postacią NAS Każdy DAS można interpretować jako szczególny NAS o funkcjach przejścia definiujących zawsze zbiór jednoelementowy: i przez analogię δ D (q, x) = p δ N (q, x) = {p} ˆδ D (q 0, m) = p ˆδ N (q 0, m) = {p}. Powyższa konstrukcja nie zmienia automatu, a co za tym idzie, jego języka i polega jedynie na reinterpretacji elementów automatu. Automaty NAS i DAS są równoważne. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 60 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.3] Twierdzenie o regularności języków Język L jest akceptowany przez pewien DAS wtedy i tylko wtedy, gdy jest akceptowany przez pewien NAS. Dowód: wynika wprost z twierdzenia o równoważności DAS i NAS: język jest regularny jeśli istnieje akceptujący go DAS DAS i NAS są sobie równoważne język jest regularny jeśli istnieje akceptujący go NAS L jest akceptowany przez DAS L jest akceptowany przez NAS Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 61 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Problem: automat ma rozpoznawać liczby całkowite. Przykładowe postaci to: 1, 12, 1, 12. Tak więc długość ciągu cyfr jest nieznana, zaś znak minus jest opcjonalny (może wystąpić lub nie). Alfabet (minimalny): Σ = {, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9} Wygodnie byłoby dopuścić przejścia od łańcucha pustego, a więc δ(q, ε), aby uwzględnić ewentualny znak minus! rozszerzenie automatu o przejścia z łańcuchem pustym oznacza wprowadzenie ε jako dozwolonego argumentu funkcji przejścia oznacza to, że automat może spontanicznie zmienić stan, podążając ścieżką diagramu podpisaną ε nie oznacza to, że włączamy ε do alfabetu Σ (patrz uwaga pod definicją ε-nas) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 62 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Mając do dyspozycji ε-przejścia, automat możemy skonstruować następująco: Diagram: Tabela: start q0 0,...,9, ε q1 0,...,9 q2 ε-nas ε 0,..., 9 q 0 {q 1 } {q 1 } q 1 {q 1, q 2 } q 2 Jest to przykład niedeterministycznego automatu skończonego z ε-przejściami: niedeterministyczność pozwala nie znać długości ciągów wejściowych ε-przejścia pozwalają uwzględnić opcjonalny znak minus Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 63 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Definicja: NAS z ε-przejściami Niedeterministyczny automat skończony z ε-przejściami (ε-nas) to automat skończony, niedeterministyczny, zdefiniowany poprzez (Q, Σ, δ, q 0, F ), gdzie Q to skończony zbiór stanów Σ to skończony zbiór symboli wejściowych (alfabet) δ to funkcje przejścia między stanami, δ : Q Σ {ε} {Q } Q q 0 Q to stan początkowy F Q to zbiór stanów akceptujących (stanów końcowych) UWAGA: Σ jest zbiorem symboli mogących pojawić się na wejściu automatu. Co prawda ε formalnie należy do każdego alfabetu, ale w przypadku ε-nas wyklucza się ten symbol z Σ. Stąd zapis Σ {ε} w definicji funkcji przejścia. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 64 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Definicja: ε-domknięcie stanu ε-domknięciem stanu q nazywamy zbiór stanów osiągalnych z q poprzez ε-przejścia. Definicja rekurencyjna: q edomk(q) [p edomk(q)] [ r : δ(p, ε) = r] [r edomk(q)] Analogicznie do poprzednich przypadków definiuje się dla ε-nas rozszerzone funkcje przejścia i język. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 65 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Definicja: rozszerzona funkcja przejścia ε-nas Rozszerzona funkcja przejścia ˆδ zdefiniowana jest rekurencyjnie tak, że to jeśli m = lx jest łańcuchem składającym się z łańcucha l i symbolu x (l Σ, x Σ), oraz jeśli ε-nas będący w stanie q po przetworzeniu łańcucha l znajdzie się w stanach {p 1,..., p k }, oraz jeśli symbol x zamienia stany {p 1,..., p k } {r 1,..., r s }, czyli ki=1 δ(p i, x) = {r 1,..., r s }, ˆδ(q, ε) = edomk(q) s ˆδ(q, m) = edomk(r j ) j=1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 66 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Definicja: język ε-nas Językiem ε-nas L(A) nazywamy zbiór łańcuchów w przeprowadzających stan początkowy automatu A w zbiór stanów, zawierający co najmniej jeden stan akceptujący: L(A) = {w : ˆδ(q 0, w) F } Formalnie definicja ta ma taką samą postać jak definicja dla NAS i analogiczną do definicji dla DAS. Różnica polega na uwzględnieniu w funkcjach ˆδ ε-przejść. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 67 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Twierdzenie o równoważności DAS i ε-nas Język L jest akceptowany przez pewien ε-nas wtedy i tylko wtedy, gdy jest regularny (czyli jest akceptowany przez pewien DAS). Aby to pokazać wystarczy udowodnić, że każdy ε-nas można sprowadzić do NAS. Ponieważ NAS i DAS są równoważne, otrzymamy powyższe twierdzenie. Oznacza ono, że dla każdego ε-nas możemy podać równoważny mu DAS. Procedura wymaga: pozbycia się przejść ε (otrzymujemy NAS) przekształcenia NAS na DAS wg schematu konstrukcji podzbiorów Formalna procedura eliminacji ε-przejść patrz Aut rozdz. 2.5.5. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 68 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Przykład równoważnych automatów ε-nas i DAS 0,...,9 przejście z ε powoduje rozgałęzienie grafu DAS start start q0 q0, ε q1 0,...,9 q1 0,...,9 0,...,9 0,...,9 q3 q2 0,...,9 q2,0,...,9 φ mamy dwa stany akceptujące DAS, odpowiadające liczbom dodatnim i ujemnym mamy stan pusty (zielona część), którego się zazwyczaj nie rysuje; reprezentuje on niewłaściwy ciąg i nie jest stanem akceptowalnym, chociaż na nim DAS może skończyć działanie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 69 / 289

Automaty deterministyczne i niedeterministyczne Aut [2.5] Podsumowanie ε-nas pozwala na najbardziej kompaktowe zakodowanie skomplikowanych operacji każde ε-przejście w ε-nas oznacza rozgałęzienie ścieżek w NAS (i duplikację części stanów powtarzających się w rozłącznych ścieżkach) znalezienie DAS równoważnego danemu NAS jest trudniejsze (procedura konstrukcji podzbiorów) tak skonstruowane DAS mogą wymagać wprowadzenia stanu pustego części diagramu nieosiągalne ze stanu początkowego formalnie istnieją, ale często się je pomija, przez co (pozornie!) diagram DAS może wyglądać na niekompletny nieosiągalność ze stanu początkowego może wynikać z zewnętrznych warunków, np. z narzuconych postaci łańcuchów wejściowych Pokazaliśmy więc, że wszystkie trzy typy automatów: DAS, NAS i ε-nas mogą być w siebie wzajemnie przekształcane. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 70 / 289

Wyrażenia regularne Aut [3.1] Wyrażenia regularne (RE, regexp) Wyrażenia regularne język opisu wzorców czy wzorce opisujące język? są wykorzystywane w zadaniach dopasowania i rozpoznawania łańcuchów, m.in. w wyszukiwarkach, parserach, analizatorach treści... technicznie, wyrażenie regularne jest przez aplikację zamieniane na odpowiedni automat prowadzi to do (słusznego) podejrzenia, że automaty i RE nawet jeśli nie są sobie równoważne, to mają wiele wspólnego konkretne symbole używane do budowanie RE różnią się pomiędzy aplikacjami i systemami, ale podstawowe zasady są niezmienne Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 71 / 289

Wyrażenia regularne Aut [3.1] Na językach można wykonać trzy podstawowe operacje. Są to: suma, którą definiuje się jako sumę zbiorów łańcuchów należących do obu języków a [a L a M] [a L M] konkatenacja, którą definiuje się jako zbiór łańcuchów utworzonych jako konkatenacje łańcuchów należących do obu języków a,b [a L b M] [ab LM] domknięcie, które jest definiowane jako suma łańcuchów należących do kolejnych konkatenacji języka z samym sobą (suma potęg języka) L = L i = L 0 L 1 L 2 = {ε} L LL... i=0 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 72 / 289

Wyrażenia regularne Aut [3.1] Definicja: algebra RE Algebrę wyrażeń regularnych definiuje się następująco: ε jest RE reprezentującym język {ε}, czyli L(ε) = {ε} jest RE reprezentującym język, czyli L( ) = jeśli R 1 i R 2 są RE, to: (suma) L(R 1 + R 2 ) = L(R 1 ) L(R 2 ) (konkatenacja) L(R 1 R 2 ) = L(R 1 )L(R 2 ) (domknięcie) L(R1) = (L(R 1 )) jeśli R jest RE, to (R) jest również RE reprezentującym ten sam język co R, L((R)) = L(R) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 73 / 289

Wyrażenia regularne Aut [3.1] UWAGI: pojedyncze symbole alfabetu są RE suma i konkatenacja RE (domknięcie (dopełnienie) jest kombinacją obu tych operacji) tworzą poprawne RE RE możemy grupować za pomocą nawiasów Kolejność wykonywania operacji na RE: domknięcie ( potęgowanie ) konkatenacja ( mnożenie ) suma Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 74 / 289

Wyrażenia regularne Aut [3.2] Twierdzenie o odpowiedniości automatu i RE Jeśli język L jest akceptowany przez pewien automat, to istnieje wyrażenie regularne, które opisuje ten język. Twierdzenie o odpowiedniości RE i automatu Jeśli język L jest opisywany przez wyrażenie regularne, to istnieje automat ε-nas, który akceptuje ten język. W połączeniu z poprzednimi twierdzeniami, otrzymujemy równoważność wszystkich czterech struktur: DAS NAS ε-nas regexp regexp ε NAS DAS NAS Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 75 / 289

Wyrażenia regularne Aut [3.2] Przykład przejścia DAS RE. Rozważmy automat DAS akceptujący ciągi bitowe zawierające co najmniej jeden symbol 0 1 0,1 start q1 0 q2 Ogólny ciąg akceptowany przez DAS ma dowolną liczbę 1 na początku 0 dowolną liczbę 0 lub 1 na końcu co zapisać można jako 1 0(0 + 1) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 76 / 289

Wyrażenia regularne Aut [3.2] Formalna procedura dla ogólnego przypadku ε-nas RE. ponumerujmy stany automatu kolejnymi liczbami, 1, 2,... oznaczmy przez R (n) ij RE, które umożliwia przejście między stanami i j wykonując skoki przez stany pośrednie o numerach co najwyżej równych n każde R (n) ij może być lub zbiorem niepustym, z uwzględnieniem ewentualnych ε-przejść można pokazać ( Aut [3.2]), że R (n) ij = R (n 1) ij + R (n 1) ( in R (n 1) nn ) R (n 1) nj czyli przejście i j przez n można zrealizować bez odwiedzania węzła n lub odwiedzając węzeł pośredni n, z możliwością zostania w n dowolnie długo (symbol ) na koniec sumuje się RE prowadzące od stanu początkowego do stanu akceptującego automatu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 77 / 289

Wyrażenia regularne Aut [3.2] Wracając do przykładu, przejścia bez węzła pośredniego można wyczytać bezpośrednio z grafu: 1 0,1 R (0) 11 = ε + 1 R (0) 12 = 0 R (0) 21 = R (0) 22 = ε + 0 + 1 start Z ogólnej reguły R (1) ij = R (0) ij + R (0) i1 przybliżenia, czyli przejścia z jednym węzłem pośrednim q 1 ( R (0) 11 ) R (0) 1j q1 0 q2 generujemy drugi rząd R (1) 11 = ε + 1 + (ε + 1)(ε + 1) (ε + 1) = 1 R (1) 12 = 0 + (ε + 1)(ε + 1) 0 = 1 0 R (1) 21 = + (ε + 1) (ε + 1) = R (1) 22 = ε + 0 + 1 + (ε + 1) 0 = ε + 0 + 1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 78 / 289

Wyrażenia regularne Aut [3.2] Analogicznie reguła R (2) ij = R (1) ij + R (1) ( i2 przybliżenia z węzłem pośrednim q 2 R (1) 22 ) R (1) 2j daje drugi rząd R (2) 11 = 1 + 1 0(ε + 0 + 1) = 1 R (2) 12 = 1 0 + 1 0(ε + 0 + 1) (ε + 0 + 1) = 1 0(0 + 1) R (2) 21 = + (ε + 0 + 1)(ε + 0 + 1) = R (2) 22 = ε + 0 + 1 + (ε + 0 + 1)(ε + 0 + 1) (ε + 0 + 1) = (0 + 1) Przejście od stanu początkowego (q 1 ) do akceptującego (q 2 ) opisywane jest wyrażeniem R (2) 12 = 1 0(0 + 1), co kończy formalnie konstrukcję. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 79 / 289

Wyrażenia regularne Aut [3.2] Przekształcenie DAS RE metodą eliminacji stanów zamieniamy opisy etykiet z symboli alfabetu na RE przekształcamy graf automatu usuwając z niego po kolei stany pośrednie rekompensujemy to, dodając etykiety z RE odpowiadające przejściom przez eliminowany stan pośredni q2 R23 R2e q3 q2 R23 + R2e (Ree)* Re3 q3 qe Re3 q1 R1e Ree q1 R1e (Ree)* Re3 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 80 / 289

Wyrażenia regularne Aut [3.2] po dojściu do automatu jednostanowego (ze stanem akceptującym) otrzymane wyrażenie jest zapamiętywane redukcję prowadzi się osobno dla każdego stanu akceptującego na koniec dodajemy wyrażenia RE odpowiadające stanom akceptującym Metoda eliminacji stanów sprowadza się do: znalezienia RE odpowiadających ścieżkom w grafie automatu (dowolnego typu), prowadzących od stanu początkowego do stanu akceptującego, zsumowaniu tych RE dla wszystkich stanów akceptujących. Jest to więc konstrukcja języka automatu (patrz definicje języków i funkcji ˆδ) i symboliczny jego zapis za pomocą umownych wyrażeń (język automatu jest regularny, więc te wyrażenia też nazywają się regularne). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 81 / 289

Wyrażenia regularne Aut [3.2] Przekształcanie RE ε-nas Generalna strategia: rozkładamy RE na najprostsze podwyrażenia symbole RE odpowiadają etykietom przejść między stanami konkatenacja RE to ciąg stanów połączonych funkcją przejścia suma RE to rozgałęzienie grafu gwiazdka odpowiada pętli zwrotnej Szczegółowe instrukcje Aut[3.2.3] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 82 / 289

Wyrażenia regularne Aut [3.2] Dowód twierdzenia równoważności RE i ε-nas. Cel: Wykazać, że dla dowolnego wyrażenia regularnego istnieje odpowiadający mu automat (w sensie równości języków). Dowód: Zauważmy, że każde wyrażenie regularne jest postaci: prymitywnej symbolu ε, zbioru pustego lub symbolu x Σ złożonej kombinacji powyższych w formie sumy R 1 + R 2, konkatenacji R 1 R 2 lub gwiazdki (domknięcia) R wyrażenia regularne mogą być również grupowane za pomocą nawiasów, (R) Grupowanie nawiasami nie zmienia języka, tj. L(R) = L((R)), więc nie musimy się tym osobno zajmować. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 83 / 289

Wyrażenia regularne Aut [3.2] Automaty odpowiadające prymitywnym postaciom RE symbol ε ε zbiór pusty symbol x x Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 84 / 289

Wyrażenia regularne Aut [3.2] Automaty odpowiadające złożonym postaciom RE suma R 1 + R 2 ε R1 ε ε R2 ε złożenie R 1 R 2 ε R1 ε R2 ε domknięcie R ε ε R ε ε Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 85 / 289

Wyrażenia regularne Aut [3.2] Automat odpowiadający dowolnemu RE buduje się dokonując dekompozycji RE tak długo, aż otrzymamy zestaw operacji wykonywanych na prymitywnych formach podstawiając pod każdy element składowy odpowiedni automat łącząc wyjścia i wejścia automatów zgodnie z kolejnością występowania odpowiadających im fragmentów w RE redukcji nadmiarowych fragmentów grafu: większość ε-przejść bezpośrednich ścieżek do stanu, a więc takich, które odpowiadają słowom nie należącym do języka automatu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 86 / 289

Wyrażenia regularne Aut [3.2] Przykład: Konstrukcja automatu odpowiadającego wyrażeniu 0 + + 1 + = 00 + 11. 0 ε 0 ε ε 0 ε ε ε ε ε 1 ε ε 1 ε ε 1 ε ε Po zredukowaniu niczego nie wnoszących ε-przejść otrzymujemy wersję z jednym (ε-nas) lub dwoma (DAS) stanami akceptującymi. 0 1 ε ε 0 1 0 1 0 1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 87 / 289

Wyrażenia regularne Aut [3.3] Korzystając z regexp w praktyce, nie stosuje się zapisu algebraicznego symbol a+b oznacza a lub b i jest zastępowany zapisem z pionową kreską a b albo zapisem w nawiasach kwadratowych [ab] Przykład: rezygnuje się z ε rezygnuje się z (0 + 1) [01] (0 1) Składnia RE w różnych systemach i aplikacjach jest różna. Istnieje pewien standard, który jest rozszerzany i poprawiany przez programistów na użytek konkretnego programu. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 88 / 289

Wyrażenia regularne Aut [3.3] Podstawowa składnia regexp w linuksie. pojedynczy znak r* zero lub więcej wystąpień r r+ jedno lub więcej wystąpień r r? zero lub jedno wystąpienie r r\{n\} dokładnie n powtórzeń r r\{n,m\} co najmniej n ale nie więcej niż m wystąpień r [abc] a lub b lub c [^abc] wszystko tylko nie a, b i c [a-c] zakres (sprawdzany po kodach znaków) ^ początek linii $ koniec linii r1\ r2 r1 lub r2 \(... \) grupowanie RE Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 89 / 289

Wyrażenia regularne Aut [3.3] Dodatkowo definiuje się klasy, wykorzystywane przez aplikacje takie jak grep i sed. [:alnum:] [:alpha:] [:blank:] [:digit:] [:lower:] [:punct:] [:space:] [:upper:] znaki alfanumeryczne (alfabet+liczby) znaki alfabetu znaki puste: SPC, TAB cyfry [a-z] znaki specjalne i interpunkcyjne TAB, NL, VT (vertical tab), FF (form feed), CR, SPC [A-Z] Zaletą posługiwania się klasą jest niewrażliwość RE na kolejność znaków w używanym kodowaniu, zależnym często od ustawionej lokalizacji. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 90 / 289

Wyrażenia regularne Aut [3.3] Przykład regexp w konwencji uniksowej/linuksowej: adres email. Warunki: email zajmuje całą linię format to login@host.domena.kod login może zawierać małe litery, cyfry, kropki, podkreślenie i myślnik (minus) host.domena podobnie jak login kod to kropka i dokładnie 2, 3 lub 4 litery (.pl,.com,.info) Jedno z rozwiązań: /^([a-z0-9_\.-]+)@([a-z0-9_\.-]+)\.([a-z]{2,4})$/ Znajdź wady tego zapisu! Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 91 / 289

Wyrażenia regularne Aut [3.3] Przykład regexp w konwencji emacs, podobny do tego, jaki wykorzystywany jest do wykrycia przez edytor końca zdania (wg systemu anglosaskiego). Warunki: zdanie kończy się odpowiednim znakiem przestankowym zdanie lub jego końcówka mogą być ujęte w nawiasy lub cudzysłowy wtedy zamykający nawias/cydzysłów stawia się po znaku przestankowym [.?!][]\" )}]* Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 92 / 289

Wyrażenia regularne Aut [3.3] Przykład bezpośrednio z edytora. Wyszukiwanie w tekście słów pasujących do RE (skrót C-M-s, komenda M-x isearch-forward-regexp) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 93 / 289

Wyrażenia regularne Aut [3.3] Przykłady za www.emacswiki.com Specjalne kombinacje z emacsa w tych przykładach: \w oznacza pojedyncze słowo C-q C-j to kombinacja oznaczająca przejście do nowej linii \< \> to znaki ograniczające wyrazy [-+[:digit:]] cyfra, + lub - \(\+\ -\)?[0-9]+\(\.[0-9]+\)? liczba z kropką dziesiętną \(\w+\) +\1\> powtarzające się wyrazy \<[[:upper:]]\w* wyraz pisany wielką literą +$ spacje na końcu linii \w\{20,\} wyraz, 20 liter lub więcej \w+ski\> wyraz kończący się na ski \(19\ 20\)[0-9]\{2\} lata 1900-2099 ^.\{6,\} 6+ znaków na początku linii ^[a-za-z0-9_]\{3,16\}$ przykładowy login <tag[^> C-q C-j ]*>\(.*?\)</tag> znacznik HTML o nazwie tag Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 94 / 289

Wyrażenia regularne Aut [3.4] Prawa algebraiczne dla regexp. Niech L, M i N będą językami regularnymi. Obowiązują wtedy dla nich następujące prawa: przemienność sumy L + M = M + L łączność sumy (L + M) + N = L + (M + N ) łączność konkatenacji (LM)N = L(MN ) element neutralny sumy + L = L + = L element neutralny konkatenacji εl = Lε = L anihilator konkatenacji L = L = prawostronna rozdzielność konkatenacji względem sumy L(M + N ) = LM + LN lewostronna rozdzielność konkatenacji względem sumy (M + N )L = ML + N L Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 95 / 289

Wyrażenia regularne Aut [3.4] Ponieważ w regexp nie występują jawnie symbole ε i, które są potrzebne do zbudowania niektórych grafów opisujących automaty, prawa dotyczące elementów neutralnych anihilatorów pozwalają na upraszczanie wyrażeń przy przejściach ε-nas regexp. Dodatkowo trzeba podkreślić, że: konkatenacja nie jest przemienna suma nie posiada anihilatora Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 96 / 289

Wyrażenia regularne Aut [3.4] Dalsze prawa i właściwości: idempotentność sumy L + L = L idempotentność domknięcia (L ) = L domknięcie zbioru pustego = {ε}, bo L( ) = L( ) = {ε} L( ) L( )L( ) = {ε} = {ε} domknięcie łańcucha pustego ε = ε przemienność z własnym domknięciem L + = LL = L L Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 97 / 289

Gramatyki, skanery i parsery Aut [5.1] Gramatyki Do tej pory mówiliśmy o językach (regularnych), czyli zbiorach łańcuchów symboli konstruowanych według pewnej reguły. Języki tożsame są automatom i wyrażeniom regularnym. Językiem może być dla pewnego języka (sic!) programowania zbiór etykiet znakujących zmienne, znakujących stałe, zbiór słów kluczowych, zbiór symboli oznaczających operatory itd. Potrzebna jest więc szersza struktura, która łączyłaby w sobie wiele języków, aby opisać np. interpreter/kompilator języka programowania. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 98 / 289

Gramatyki, skanery i parsery Aut [5.1] Definicja: gramatyka bezkontekstowa Gramatyką bezkontekstową G nazywamy czwórkę G = (V, T, P, S), gdzie V to zbiór zmiennych każda zmienna jest tożsama z jednym językiem; jedna ze zmiennych jest wyróżniona odpowiadając symbolowi początkowemu S, reszta to pomocnicze klasy łańcuchów T to zbiór symboli końcowych jest to niepusty zbiór skończony, zawierający łańcuchy definiowanego języka P to zbiór produkcji skończony zbiór reguł pozwalających na rekurencyjne definiowanie języka S to symbol początkowy Można to w zwarty sposób zapisać następująco: T T V = S V P V (T V ) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 99 / 289

Gramatyki, skanery i parsery Aut [5.1] Słowniczek pojęć: zmienna = symbol nieterminalny symbol końcowy = symbol terminalny, terminal łańcuch języka = leksem, token Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 100 / 289

Gramatyki, skanery i parsery Aut [5.1] Każda produkcja jest parą (A, ζ), gdzie A V jest pojedynczą zmienną (symbolem nieterminalnym), ζ (T V ) jest dowolnym symbolem terminalnym lub nieterminalnym, również łańcuchem pustym. Zapisuje się je w postaci: A ζ Innymi słowy produkcja jest regułą typu: głowa ciało gdzie głowa to zmienna definiowana przez daną produkcję ciało to ε lub łańcuch utworzony ze zmiennych i terminali Cała reguła określa sposób tworzenia łańcuchów w języku reprezentowanym przez zmienną będącą głową. W praktyce produkcje tożsame są wyrażeniom regularnym, jednak tych ostatnich nie używa się jawnie na poziomie gramatyk. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 101 / 289

Gramatyki, skanery i parsery Aut [5.1] Tak więc gramatyka jest konstrukcją szerszą ) niż omawiane do tej pory: operuje na więcej niż jednym języku, a więc odpowiada jej cały zestaw automatów lub pojedynczy automat złożony RE spełniają rolę wzorców łańcuchów akceptowanych przez ten automat produkcje wykorzystują RE do określania reguł budowania akceptowanych łańcuchów ) Szerokość gramatyki nie polega na tym, że zawiera w sobie informacje, których nie ma w DAS i RE, ale informacje te podane są w sposób jawny poprzez produkcje, które można utworzyć rozkładając RE na czynniki pierwsze. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 102 / 289

Gramatyki, skanery i parsery Aut [5.1] Przykład: Gramatyka opisująca liczby parzyste w zapisie binarnym. Alfabetem binarnym jest Σ = {0, 1}. Każda liczba parzysta w zapisie binarnym kończy się symbolem 0. W zapisie regexp będzie to: (0 + 1) 0 Zbiorem zmiennych V jest więc zbiór zawierający element reprezentujący liczbę parzystą w zapisie binarnym {lparz}. Ponieważ mamy tylko jedną zmienną, pełni ona automatycznie rolę symbolu początkowego S = lparz. Zbiorem terminali T jest alfabet binarny {0, 1}. Zbiór produkcji P nazwiemy P rod i zawiera on: lparz 0 lparz lparz 1 lparz lparz 0 Ostatecznie gramatykę zapisujemy jako: G = (V, T, P, S) = ({lparz}, {0, 1}, P rod, lparz). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 103 / 289

Gramatyki, skanery i parsery Aut [5.1] Definicja: łańcuch bezpośrednio wyprowadzalny Mamy dane gramatykę G = (V, T, P, S) oraz dwa łańcuchy ζ, η (T V ) (być może puste). Łańcuch η jest bezpośrednio wyprowadzalny z łańcucha ζ: ζ G η wtedy i tylko wtedy, gdy istnieją łańcuchy α, β, γ (T V ) produkcja A γ takie, że ζ = αaβ η = αγβ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 104 / 289

Gramatyki, skanery i parsery Aut [5.1] Definicja: łańcuch pośrednio wyprowadzalny Łańcuch η jest pośrednio wyprowadzalny z ζ, jeśli istnieje ciąg łańcuchów α i, i = 1,..., n, takich, że α 1 = ζ α i G α i+1 α n = η Innymi słowy: łańcuch bezpośrednio wyprowadzalny jest możliwy do osiągnięcia poprzez zastosowanie pojedynczej produkcji łańcuch pośrednio wyprowadzalny jest możliwy do osiągnięcia po zastosowaniu szeregu produkcji Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 105 / 289

Gramatyki, skanery i parsery Aut [5.1] Do definiowania gramatyk używa się najczęściej wyprowadzeń, czyli produkcji w kierunku głowa ciało. W ten sposób z symboli/łańcuchów należących do języka danej zmiennej budujemy nowe, które siłą rzeczy również do tego języka należą. Można też używać wnioskowania rekurencyjnego, tj. odwrócić kierunek produkcji głowa ciało. Bierze się wtedy wynikowy łańcuch i dopasowuje do niego istniejące produkcje, rozkładając go na podłańcuchy. Jeśli w ten sposób da się otrzymać najprostsze symbole, analizowany łańcuch musi należeć do języka głowy produkcji. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 106 / 289

Gramatyki, skanery i parsery Aut [5.1] W analogii do uogólnionych funkcji przejścia ˆδ, które przyjmowały jako argument całe łańcuchy, a nie pojedyncze symbole, definiuje się uogólnione wyprowadzenie, oznaczające zero lub więcej kroków pośrednich wyprowadzenia: α G β Zbiorczo oznacza to, że: przy zero krokach pośrednich β będzie łańcuchem bezpośrednio wyprowadzalnym z α przy jednym lub więcej krokach pośrednich β będzie łańcuchem pośrednio wyprowadzalnym z α Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 107 / 289

Gramatyki, skanery i parsery Aut [5.1] Przykład: Niech gramatyka G = (V, T, P, S) opisuje wyrażenia arytmetyczne, zbudowane z trzech zmiennych (sic!) x, y, z i czterech działań +,,, / oraz nawiasów (, ). Zbiór zmiennych (symboli nieterminalnych) Możemy wyodrębnić dwa typy obiektów: zmienną i operator. Tak więc zbiór zmiennych składa się z V = {zmienna, operator, S} gdzie S to symbol początkowy. Zbiór symboli terminalnych Terminalami są wszystkie używane w zapisie wyrażeń arytmetycznych symbole, a więc T = {x, y, z, +,,, /, (, )} Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 108 / 289

Gramatyki, skanery i parsery Aut [5.1] Produkcje Reguły tworzenia nowych łańcuchów i decydowania, czy dany łańcuch jest poprawnym wyrażeniem arytmetycznym. Ogólny schemat wygląda tak: S zmienna S S operator S S (S) zmienna x zmienna y zmienna z operator + operator operator operator / Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 109 / 289

Gramatyki, skanery i parsery Aut [5.1] Problem: sprawdzić, czy x + (y/x) jest poprawnym wyrażeniem arytmetycznym w sensie omawianej gramatyki. Wyrażenie to będzie poprawne, jeśli da się podać ciąg wyprowadzeń, prowadzący od symbolu początkowego, do tego wyrażenia: S G x + (y/x) Przykładowy ciąg: S G S operator S G S operator (S) G S operator (S operator S) G S operator (S/S) G S + (S/S) G zmienna + (S/S) G zmienna + ( zmienna /S) G zmienna + ( zmienna / zmienna ) G x + ( zmienna / zmienna ) G x + (y/ zmienna ) G x + (y/x) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 110 / 289

Gramatyki, skanery i parsery Aut [5.1] Podane wyprowadzenie jest bałaganiarskie w kwestii kolejności zamiany wyrażeń i podstawień terminali. Wyprowadzenie lewostronne to takie, w którym budujemy wyrażenie od lewej do prawej. Pierwsza zmienna od lewej zastępowana jest jednym z ciał jej produkcji. Wyprowadzenie prawostronne to takie, w którym budujemy wyrażenie od prawej do lewej. Pierwsza zmienna od prawej zastępowana jest jednym z ciał jej produkcji. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 111 / 289

Gramatyki, skanery i parsery Aut [5.1] Poprzednie wyprowadzenie w wersji czysto lewostronnej: S G,l S operator S G,l G,l G,l G,l G,l G,l G,l G,l G,l zmienna operator S x operator S x + S x + (S) x + (S operator S) x + (zmienna operator S) x + (y operator S) x + (y/s) x + (y/zmienna) G,l x + (y/x) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 112 / 289

Gramatyki, skanery i parsery Aut [5.1] Definicja: język gramatyki Językiem gramatyki G = (V, T, P, S) nazywamy zbiór łańcuchów symboli końcowych, dla których istnieje wyprowadzenie z symbolu początkowego L(G) = {w T : S G w} Jeśli G jest gramatyką bezkontekstową, to L(G) jest językiem bezkontekstowym. Definicja: forma zdaniowa Wyprowadzenie z symbolu początkowego nazywane jest formą zdaniową. Wyróżnia się lewo- i prawostronne formy zdaniowe. Tak więc L(G) jest zbiorem form zdaniowych, składających się wyłącznie z symboli końcowych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 113 / 289

Gramatyki, skanery i parsery Aut [5.2] Do reprezentacji wyprowadzeń używa się grafu zwanego drzewem wyprowadzenia. Jeśli G = (V, T, P, S) jest gramatyką, to drzewa wyprowadzeń dla G podlegają regułom wierzchołki wewnętrzne opisane są zmiennymi z V liście opisane są zmiennymi, terminalami lub łańcuchem pustym ε jeśli liść opisany jest przez ε to musi to być jedyny liść dla tego rodzica jeśli wierzchołek opisany zmienną A V ma liście opisane symbolami X 1, X 2,..., X n, to musi istnieć produkcja A X 1 X 2... X n (X i ε) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 114 / 289

Gramatyki, skanery i parsery Aut [5.2] Przykład: część gramatyki opisującej prefiksowe działanie operatora dwuargumentowego. Przyjmijmy, że mamy daną gramatykę z symbolami końcowymi Wyprowadzenie lewostronne Odpowiadające mu drzewo wyprowadzenia op2 (operator binarny, S prefiksowy), var S op2 S S G,l (zmienna), +, a, b i być może innymi. Istnieją G,l + S S op2 S S też odpowiednie + var S G,l produkcje. Sprawdzamy, + a S czy wyrażenie + a b jest G,l + var var poprawne w kontekście + a var a b G,l tej gramatyki. G,l + a b Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 115 / 289

Gramatyki, skanery i parsery Aut [5.2] Twierdzenie o równoważności Niech G = (V, T, P, S) będzie gramatyką. Poniższe procedury rozstrzygające, czy łańcuch końcowy w należy do języka zmiennej A są sobie równoważne: wnioskowanie rekurencyjne uogólnione wyprowadzenie A G w uogólnione wyprowadzenie lewostronne A G,l w uogólnione wyprowadzenie prawostronne A G,p w konstrukcja drzewa wyprowadzenia o korzeniu A i łańcuchu wynikowym w Ścisłe dowody Aut[5.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 116 / 289

Gramatyki, skanery i parsery Aut [5.2] Równoważność drzewa i wyprowadzenia lewostronnego szkic dowodu. Przypuśćmy, że drzewo opisane jest korzeniem A V i liśćmi w = w 1 w 2... w k, w i T. 1 Jeśli wysokość drzewa n = 1, to musi istnieć produkcja A w, która jest jednocześnie wyprowadzeniem lewostronnym A G,l w. 2 Jeśli wysokość drzewa to n + 1 i po n krokach mamy to A G,l X 1 X 2... X k X i jest albo elementem z T, wtedy w i = X i X i jest głową produkcji X i w i 3 W obu przypadkach, postępując w kierunku rosnących i otrzymamy lewostronne wyprowadzenie łańcucha w. Istnienie wyprowadzenia prawostronnego pokazuje się w analogiczny sposób. Istnienie wyprowadzenia nieuporządkowanego wynika bezpośrednio z istnienia wyprowadzeń lewo- i prawostronnych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 117 / 289

Gramatyki, skanery i parsery Aut [5.2] Równoważność wyprowadzeń i wnioskowania szkic dowodu. 1 Wnioskowanie rekurencyjne polega na sprawdzeniu, że analizowany symbol jest ciałem produkcji, bezpośrednio lub pośrednio wyprowadzalnym. 2 Jeśli więc istnieje wyprowadzenie, które dowodzi, że dany łańcuch należy do języka gramatyki, to wystarczy odwrócić operacje, aby z wyprowadzeń otrzymać wnioskowanie. Formalnie dowód przeprowadza się rekurencyjnie względem liczby kroków wyprowadzenia. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 118 / 289

Gramatyki, skanery i parsery Aut [5.3] Zastosowanie gramatyk w programowaniu Języki programowania dzialą się na dwa typy: interpretowane (np. awk, ruby, bash, python, perl) kompilowane (np. C, fortran, java, haskell) Istnieją jeszcze języki znacznikowe (np. html, xml), ale nie są to najczęściej języki programowania w takim sensie, w jakim tego określenia używam na wykładzie. Proces prowadzący od odczytu kodu do jego wykonania zawiera kilka etapów. W fazie wstępnej mamy: analizę leksykalną analizę syntaktyczną analizę semantyczną Później albo działa kompilator generujący kod wykonywalny, albo interpreter uruchamiający kod w locie. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 119 / 289

Gramatyki, skanery i parsery Aut [5.3] Typowy schemat kompilacji wejściowy ciąg znaków Skaner tokeny Parser drzewo parsowania Generator kodu pośredniego kod pośredni Optymalizacja kod pośredni Tablica symboli kod maszynowy Generator kodu maszynowego Optymalizacja finalny kod maszynowy Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 120 / 289

Analiza leksykalna Gramatyki, skanery i parsery Aut [5.3] jest wykonywana przez skaner jest wykonywana na podstawie reguł, które do skanera dostarczone są z zewnątrz (czyli ktoś musi taki skaner najpierw oprogramować) wejściowy ciąg znaków Skaner tokeny polega na analizie kodu i klasyfikacji poszczególnych łańcuchów znaków za pomocą znaczników ( tokenizacja kodu ); tokeny to zmienne późniejszej gramatyki Tablica symboli skaner tworzy plik z rozbiorem leksykalnym analizowanego kodu, który staje się jednym z plików wejściowych dla parsera w linuksie używany jest flex (fast lexical analyser generator), kompatybilny z leciwym już programem lex Skaner (analizator leksykalny) zazwyczaj posługuje się językiem regularnym, w którym alfabetem są znaki używane do zapisania kodu. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 121 / 289

Gramatyki, skanery i parsery Aut [5.3] Analiza syntaktyczna jest wykonywana przez parser (generator parserów) opiera się na regułach parsera oraz pliku z danymi pochodzącymi od skanera tokeny Parser Tablica symboli drzewo parsowania reguły parsera to produkcje gramatyki zdefiniowane na tych samych tokenach, które były używane w regułach skanera parser tworzy pełną gramatykę (w postaci drzewa wyprowadzeń, tu nazywanym drzewem parsowania) w linuksie parser (funkcję parsującą) generuje bison, następca programu yacc (parser generator) Generator parserów działa w oparciu o język bezkontekstowy, zdefiniowany na tokenach wygenerowanych wcześniej przez skaner. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 122 / 289

Gramatyki, skanery i parsery Aut [5.3] Analiza semantyczna analiza znaczeniowa, czyli przypisanie do abstrakcyjnych tworów opisywanych tokenami (zmiennymi gramatyki) takimi jak wyrażenie, etykieta, zmienna, operator itd. konkretnych wartości analizowany jest kod pośredni generowany przez skaner i parser zajmuje się tym część kompilatora sprawdzane są zgodności typów zmiennych Tablica symboli drzewo parsowania Generator kodu pośredniego wyszukiwane są takie same etykiety w programie i twory im przypisane są utożsamiane analizowane są pętle i rekurencje generowane komunikaty o błędach tworzony jest nowy kod pośredni kod pośredni Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 123 / 289

Gramatyki, skanery i parsery Aut [5.3] Przykład: Analiza wyrażenia a x + b na podstawie gramatyki z produkcjami (wprowadzamy zapis skrótowy) wyr ident liczba wyr ( wyr ) wyr op wyr op + / Analiza leksykalna Analiza syntaktyczna Analiza semantyczna a x+b a b x + ident op wyr wyr op wyr op wyr + ident(a) * ident(x) wyr ident(b) a * x + b Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 124 / 289

Gramatyki, skanery i parsery Aut [5.3] Przykład: Problemy w analizie leksykalnej. Zazwyczaj skaner ignoruje białe znaki w kodzie, tj. nie ma znaczenia, jak programista graficznie zapisał kod. W niektórych językach wcięcia są istotnym elementem składni (fortran77 (fixed-format), haskell), w innych nie, ale zapisy 1 a = 2.0 2 a =2.0 są najczęściej nierozróżnialne. Porównaj dwa kody: 1 DO 5 I = 1. 25 1 DO 5 I = 1,25 F77 ignoruje spacje, więc w pierwszym przykładzie skaner powinien rozpoznać podstawienie pod zmienną DO5I wartości 1.25, zaś w drugim przykładzie początek pętli, zamkniętej etykietą 5 (tej etykiety to już będzie poszukiwał parser, aby zadecydować, czy jest to prawidłowa konstrukcja języka), o zmiennej I przyjmującej kolejne wartości 1,2,...,25. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 125 / 289

Gramatyki, skanery i parsery Aut [5.3] Problem: skaner musi jakoś zakwalifikować napotkaną konstrukcję nie wie, co znajduje się dalej (kropka czy przecinek?) musi czytać kod z pewnym nadmiarem naprzód i swoje decyzje uzależniać od tego, co następuje kod może być błędny! Wniosek: skaner z buforem odczytu (pamięcią) automaty ze stosem Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 126 / 289

Gramatyki, skanery i parsery Aut [5.3] Dalsze losy kodu: kod pośredni trafia do interpretera i jest wykonywany kompilatora i jest tłumaczony na kod maszynowy Kompilator dokonuje szeregu dalszych operacji, m.in. optymalizacji kodu. W fazie wstępnej następuje optymalizacja niezależna od architektury (np. rozwijane są zagnieżdżone pętle), potem optymalizacja pod konkretną architekturę (wykorzystanie rozszerzeń instrukcji CPU, rejestrów, wielowątkowość, współbieżność itd.). Pod koniec może mieć miejsce linkowanie dynamiczne lub linkowanie statyczne. W przypadku rozrzucenia programu po wielu plikach schemat kompilacji musi jeszcze obejmować scalanie kodu. Jeśli program wykorzystuje podprogramy (procedury, funkcje, biblioteki zewnętrzne) musi nastąpić test spójności pełnego kodu. Jak łatwo się domyśleć, bardzo wygodne dynamiczne typowanie, obecne w niektórych językach (np. ruby, awk), nie jest proste do zrealizowania na poziomie kompilatora! Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 127 / 289

Gramatyki, skanery i parsery Aut [5.3] Przykład lex: analiza stdin, rozpoznawanie liczb i słów. Kod dla lex a składa się w najprostszej wersji z dopasowania (regexp) i akcji 1 %{ 2 # include <stdio.h> 3 %} 4 5 %% 6 [0123456789]+ printf (" NUMBER \ n "); 7 [a-za -Z][a-zA -Z0-9]* printf (" WORD \n "); 8 %% Po kompilacji tworzony jest kod w C. Część ograniczona %{ i %} jest bezpośrednio przenoszona do tego pliku. Reszta jest generowana z reguł postaci regexp - akcja. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 128 / 289

Gramatyki, skanery i parsery Aut [5.3] Przykład kalkulator lex: wyodrębnianie liczb w ciągach znakowych, ignorowanie znaków białych 1 %{ 2 # include "y. tab.h" 3 extern int yylval ; 4 %} 5 6 %% 7 [0-9]+ { yylval = atoi ( yytext ); 8 printf (" liczba %d\n", yylval ); 9 return NUMBER ; } 10 [ \t] { printf (" białe znaki \n "); } 11 \n { printf (" koniec linii \n "); 12 return 0; } 13. { printf (" inne dane \"% s \"\ n", yytext ); 14 return yytext [0]; } 15 %% Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 129 / 289

Gramatyki, skanery i parsery Aut [5.3] Przykład kalkulator yacc: yacc może teraz rozpoznawać wyrażenia arytmetyczne i je obliczać 1 %{ 2 # include <stdio.h> 3 %} 4 % token NAME NUMBER 5 %% 6 stmt : NAME = expr { printf (" przypisanie % s 7 do %d\n",$1,$3) } 8 expr { printf ("= %d\n", $1) } 9 ; 10 expr : expr + NUMBER { $$ = $1 + $3 } 11 expr - NUMBER { $$ = $1 - $3 } 12 NUMBER { $$ = $1 } 13 %% 14 int main ( void ) { 15 return yyparse (); 16 } Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 130 / 289

Gramatyki, skanery i parsery Aut [5.3] Rodzaje błędów: leksykalne (słownikowe) pomylone identyfikatory zmiennych, słów kluczowych, brak cudzysłowów syntaktyczne (składniowe) brak znaków specjalnych (przecinek, kropka, średnik), niedomknięte nawiasy, niedokończone struktury blokowe (if...then...fi, begin...end) semantyczne (znaczeniowe) zazwyczaj jest to niezgodność typów, czyli użycie czegoś w kontekście, w którym to coś nie ma sensu (np. operacja na liście wykonywana na zmiennej napisowej) logiczne błędy programisty, który koduje nie to, co zamierza, np. używa operatora porównania == zamiast przypisania = algorytmiczne błędy programisty, który używa niepoprawnego algorytmu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 131 / 289

Gramatyki, skanery i parsery Aut [5.4] Dla wielu gramatyk dane (poprawne) wyrażenie można wyprowadzić na kilka sposobów. Pomimo tego, że efekt końcowy wyprowadzenia jest ten sam, wyprowadzenia te mogą nie być sobie równoważne na poziomie semantycznym. Wieloznaczność gramatyki polega na tym, że można podać kilka nierównoważnych sobie poprawnych wyprowadzeń danego łańcucha. przeprojektowanie gramatyki może rozwiązać problem wieloznaczności nie wszystkie gramatyki można poprawić istnieją takie, w których niektóre łańcuchy zawsze będą określone wieloznacznie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 132 / 289

Gramatyki, skanery i parsery Aut [5.4] Porównaj: oba poniższe wyprowadzenia prowadzą do wyrażenia a x + b, ale lewe sugeruje poprawną kolejność działań (a x) + b, natomiast prawe niepoprawną a (x + b). Poprawność i niepoprawność istnieje jedynie na poziomie syntaktycznym, gdyż nadajemy symbolom i + znaczenie i właściwości mnożenia i dodawania. wyr wyr wyr op wyr wyr op wyr wyr op wyr + ident(b) ident(a) * wyr op wyr ident(a) * ident(x) ident(x) + ident(b) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 133 / 289

Gramatyki, skanery i parsery Aut [5.4] W językach programowania problem wieloznaczności można rozwiązać (przynajmniej częściowo) deklarując lewostronną łączność operatorów oznacza to, że czytając wyrażenie od lewej do prawej operator przykleja się do wyrażenia po jego lewej stronie w niektórych sytuacjach deklaruje się łączność prawostronną deklarując priorytety rozpatrywania operatorów priorytety mogą np. łamać regułę ścisłego lewostronnego wyprowadzania poprawności wyrażeń przez funkcję parsującą Chcąc zadeklarować standardowe reguły arytmetyczne w yacc u należy w kodzie przed definicją gramatyki podać deklaracje 1 % left - + 2 % left * / 3 % nonassoc UMINUS gdzie UMINUS jest minusem przed liczbą ujemną. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 134 / 289

Automaty ze stosem Aut [6.1] Automaty ze stosem Aby móc analizować partie tekstu, a nie pojedyncze symbole, skaner/parser potrzebuje jakiegoś rodzaju pamięci. Odpowiednikiem takiego urządzenia w teorii automatów jest automat ze stosem (AZS). Jest to zwykły automat, który czyta i przetwarza symbole wejściowe ma do dyspozycji (nieograniczony) stos, na który może odkładać lub z niego zdejmować symbole przejścia między stanami uzależnione mogą być od obecnego stanu, analizowanego symbolu i szczytu stosu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 135 / 289

Automaty ze stosem Aut [6.1] Stos obowiązuje kolejka FILO w każdym cyklu automat zastępuje łańcuch ze szczytu stosu x nowym y, co oznacza się x/y jeśli y = ε to efektywnie zdejmowany jest szczytowy łańcuch ze stosu i cały stos podjeżdża w górę jeśli y = x to stos się nie zmienia jeśli y = zx, to efektywnie do stosu dodawany jest nowy łańcuch z na jego szczycie stos jest nieograniczony, dlatego automaty ze stosem są automatami skończonymi (skończona liczba stanów), mogącymi zapamiętać nieskończoną ilość informacji praktyczna realizacja automatu będzie oczywiście miała swoje ograniczenia (stos będzie ograniczony rozmiarem dostępnej pamięci fizycznej) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 136 / 289

Automaty ze stosem Aut [6.1] Definicja: automat ze stosem Automat ze stosem (AZS) to automat zdefiniowany poprzez (Q, Σ, Γ, δ, q 0, Z 0, F ), gdzie Q to skończony zbiór stanów Σ to skończony zbiór symboli wejściowych (alfabet) Γ to skończony alfabet stosowy, czyli zbiór symboli, które można odkładać na stos δ to funkcje przejścia między stanami, δ : (Q Σ {ɛ} Γ) {Q Γ } q 0 Q to stan początkowy Z 0 Γ początkowy symbol na stosie F Q to zbiór stanów akceptujących (stanów końcowych) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 137 / 289

Automaty ze stosem Aut [6.1] UWAGI: 1. Funkcja przejścia δ(q, a, s) (p, s ) opisuje więc, że jeśli automat znajduje się w stanie q, analizuje symbol wejściowy a, zaś na szczycie stosu znajduje się symbol stosowy s, to automat ten może zmienić swój stan na p, zastępując s na stosie symbolem s. Pełną przeciwdziedziną funkcji δ(q, a, s) jest zbiór wszystkich możliwych par {(p, s )}. 2. Stan p i symbol s stanowią nierozerwalną parę. Jeśli δ(q, a, s) = {(p 1, s 1), (p 2, s 2)} to niedozwolone jest np. przejście (q, a, s) (p 1, s 2 ). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 138 / 289

Automaty ze stosem Aut [6.1] 3. Diagramy AZS rysuje się jak poprzednio, tylko konieczne jest uwzględnienie również stanu stosu przy symbolach przejścia. Mając funkcję δ(q, a, s) (p, s ) i chcąc opisać linię pomiędzy stanami q i p należy nadać jej etykietę a, s/s. Definicja: opis chwilowy AZS Do pełnego opisu chwilowej konfiguracji AZS podaje się trójkę (q, w, s) gdzie q to obecny stan automatu w jest nieprzeanalizowaną jeszcze częścią wejścia s zawartością stosu (w kierunku od lewej do prawej: wierzchołek dno stosu) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 139 / 289

Automaty ze stosem Aut [6.1] Definicja: ewolucja AZS ( ) Niech P = (Q, Σ, Γ, δ, q 0, Z 0, F ) będzie AZS. Przypuśćmy, że δ(q, a, s) (p, s ). Wtedy dla wszystkich w Σ oraz γ Γ zachodzi (q, aw, sγ) (p, w, s γ) Oznacza to, że to co pozostaje na wejściu (w), jak również cały stos za wyjątkiem jego szczytu (γ), nie mają wpływu na zmianę konfiguracji AZS. W podobny sposób wprowadza się ciąg ewolucyjny opisujący zero lub więcej zmian konfiguracji AZS. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 140 / 289

Automaty ze stosem Aut [6.1] Twierdzenie o ewolucji AZS Niech P = (Q, Σ, Γ, δ, q 0, Z 0, F ) będzie AZS. Jeśli (q, a, s) (p, b, s ) to dla wszystkich w Σ oraz γ Γ (q, aw, sγ) (p, bw, s γ) Twierdzenie odwrotne o ewolucji AZS Niech P = (Q, Σ, Γ, δ, q 0, Z 0, F ) będzie AZS. Jeśli (q, aw, sγ) (p, bw, s γ) to również (q, a, s) (p, b, s ) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 141 / 289

Automaty ze stosem Aut [6.2] Definicja: język AZS akceptacja przez stan końcowy Niech P = (Q, Σ, Γ, δ, q 0, Z 0, F ) będzie AZS. Językiem akceptowanym przez P nazywamy zbiór L(P ) = {w : (q 0, w, Z 0 ) (q, ε, γ), q F, γ Γ } Formalna definicja mówi, że L(P ) to zbiór takich łańcuchów wejściowych, które przeprowadzają AZS ze stanu początkowego do jakiegokolwiek stanu akceptującego, przy czym zawartość stosu po tej operacji nie ma znaczenia. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 142 / 289

Automaty ze stosem Aut [6.2] Dla AZS istnieje druga metoda akceptacji (po akceptacji poprzez stany końcowe), polegająca na doprowadzeniu do opróżnienia stosu. Definicja: akceptacja poprzez pusty stos (null stack) Dla każdego AZS P = (Q, Σ, Γ, δ, q 0, Z 0, F ) definiujemy zbiór N(P ) = {w : (q 0, w, Z 0 ) (q, ε, ε), q Q} Zgodnie z definicją, N(P ) jest zbiorem łańcuchów wejściowych, które przeprowadzają AZS ze stanu początkowego w stan, w którym na stosie nie ma żadnych symboli (null stack). N(P ) definiuje klasę języków L = N(P ), akceptowanych w ten specyficzny sposób przez automat ze stosem. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 143 / 289

Automaty ze stosem Aut [6.2] Przykład: Chcemy zdefiniować automat, który będzie analizował kod programu sprawdzając, czy każdy nawias otwierający "(" ma swój nawias zamykający ")". Problemy: nie wiemy, jak długi tekst znajduje się w nawiasie nawiasy mogą się zagnieżdżać Kiedy automat na pewno natrafi na błąd? Gdy liczba nawiasów zamykających przekroczy chwilową liczbę napotkanych nawiasów otwierających. Widać więc, że automat musi mieć pamięć, w której będzie przechowywany licznik nawiasów. Właściwym typem automatu dla tego problemu jest automat wyposażony w stos. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 144 / 289

Automaty ze stosem Aut [6.2] Załóżmy, że liczba symboli na stosie będzie zwiększana za każdy napotkany nawias ( i zmniejszana o jeden za każdy nawias ). Wejście zawierające błąd będzie zerowało stos AZS. Czyli automat nie tyle będzie sprawdzał poprawność, co niepoprawność wejścia. ),Z/ P N = ({q}, {(, )}, {Z}, δ N, q, Z, {q}) (,Z/ZZ ε gdzie po kolei podane są: pojedynczy stan q alfabet wejściowy (nawiasy) pojedynczy symbol stosowy δ N (q, (, Z) = {(q, ZZ)} δ N (q, ), Z) = {(q, ε)} stan początkowy początkowy stan stosu (pojedynczy symbol Z) stan akceptujący (nie gra roli) start q etykieta ), Z/ε oznacza napotkanie symbolu ), przy symbolu Z zdejmowanym ze szczytu stosu etykieta (, Z/ZZ oznacza napotkanie symbolu (, przy symbolu Z dodawanym na szczyt stosu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 145 / 289

Automaty ze stosem Aut [6.2] UWAGI: omawiany automat w praktyce będzie częścią większej struktury, która odsyła do niego tylko napotkane nawiasy stos pełni rolę licznika każdy nawias zmienia liczbę odłożonych na stosie symboli symbol stosowy nie jest istotny, ważne żeby był struktura automatu nie zawiera informacji o sposobie akceptowania przez niego łańcuchów akceptowanie przez opróżnienie stosu jest umową (chociaż automat z pojedynczym stanem można o to podejrzewać...) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 146 / 289

Automaty ze stosem Aut [6.2] Automat akceptujący ten sam język poprzez stan końcowy, a nie wyzerowanie stosu, będzie miał następującą strukturę: ),Z/ε (,Z/ZZ start q0 ε,z0/zz0 q1 ε,z0/ε q2 P F = ({q 0, q 1, q 2 }, {(, )}, {Z, Z 0 }, δ F, q 0, Z 0, {q 2 }) δ F (q 0, ε, Z 0 ) = {(q 1, ZZ 0 )} δ F (q 1, (, Z) = {(q 1, ZZ)} δ F (q 1, ), Z) = {(q 1, ε)} δ F (q 1, ε, Z 0 ) = {(q 2, ε)} Można udowodnić, że dla każdego AZS akceptującego język poprzez stan końcowy istnieje AZS akceptujący ten sam język poprzez wyzerowanie stosu i na odwrót. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 147 / 289

Automaty ze stosem Aut [6.2] Twierdzenie o odpowiedniości P N i P F Jeśli AZS P N akceptuje język L przez pusty stos, L = N(P N ), to istnieje AZS P F akceptujący ten sam język przez stan końcowy, L = L(P F ). Konstrukcja: ε,x0/ ε ε,x0/ ε start q0 ε,x0/z0x0 p0 ε,x0/ ε qf PN ε,x0/ ε gdzie X 0 jest symbolem początkowym stosu P F, a Z 0 symbolem początkowym stosu P N. P F przechodzi do swojego stanu akceptującego q f gdy P N opróżni stos (na szczycie stosu pojawi się symbol X 0 ). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 148 / 289

Automaty ze stosem Aut [6.2] Twierdzenie o odpowiedniości P F i P N Jeśli AZS P F akceptuje język L przez stan końcowy, L = L(P F ), to istnieje AZS P N akceptujący ten sam język przez pusty stos, L = N(P N ). Konstrukcja: ε,?/ε start p0 ε,z0/x0z0 q0 ε,?/ε p ε,?/ε PF ε,?/ε gdzie? oznacza dowolny symbol stosowy. ε-przejścia, jakie następują po wejściu P F w jakikolwiek stan akceptujący powodują opróżnienie stosu bez pobierania kolejnego symbolu z wejścia. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 149 / 289

Automaty ze stosem Aut [6.3] Twierdzenie o odpowiedniości GBK i P N Niech G = (V, T, Q, S) będzie gramtyką bezkontekstową. Automat ze stosem akceptujący język tej gramatyki przez pusty stos, N(P N ) = L(G), ma postać: P N = ({q}, T, V T, δ, q, S, F ) gdzie dla każdej zmiennej A V i każdego symbolu końcowego a T δ(q, ε, A) = {(q, β) : A β jest produkcją G} δ(q, a, a) = {(q, ε)} Funkcje innej postaci niż dwie wymienione wyżej są puste δ(...) =. AZS jest jednostanowy, tak więc operuje tylko zawartością stosu automat odgaduje produkcje i jeśli uda mu się doprowadzić łańcuch do jakiegoś symbolu terminalnego opróżnia stos Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 150 / 289

Automaty ze stosem Aut [6.3] Przykład: Gramatyka wyrażeń arytmetycznych dana jest produkcjami (v to zmienna, o to operator) S v S o S (S) v x y z o + / Symbole terminalne T = {x, y, z, +,,, /, (, )} oraz symbole nieterminalne V = {v, o} tworzą łącznie alfabet stosowy Γ = V T. Funkcje przejścia AZS o pojedynczym stanie q to: δ(q, ε, S) = {(q, v), (q, S o S), (q, (S))} δ(q, ε, v) = {(q, x), (q, y), (q, z)} δ(q, ε, o) = {(q, +), (q, ), (q, ), (q, /)} δ(q, t, t) = {(q, ε)} (dla wszystkich t T ) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 151 / 289

Automaty ze stosem Aut [6.3] Twierdzenie o odpowiedniości AZS i GBK Niech P = (Q, Σ, Γ, δ, q 0, Z 0 ) będzie automatem ze stosem. Wtedy istnieje gramatyka bezkontekstowa G taka, że L(G) = N(P ). Dowód Aut[6.3.2] Na podstawie poznanych twierdzeń wnioskujemy, że są sobie równoważne: RE GBK AZS akceptujący przez stany końcowe AZS akceptujący przez pusty stos Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 152 / 289

Automaty ze stosem Aut [6.4] Kwestia determinizmu automatów ze stosem. W ogólności AZS są niedeterministyczne, tj. w danym stanie, przy określonym wejściu i symbolu stosowym, mają określone więcej niż jedno możliwe przejście ( przykłady w Aut rozdz. 6). Jeśli odpowiednio ograniczymy nieoznaczoność wyborów kolejnych stanów chwilowych AZS możemy utworzyć deterministyczny automat ze stosem. Definicja: deterministyczny AZS Deterministyczny automat ze stosem (DAZS) jest AZS, który spełnia warunki dla dowolnych argumentów (a może być ε lub dowolnym symbolem alfabetu Σ) δ(q, a, X) jest zbiorem co najwyżej jednoelementowym jeśli dla pewnego a Σ mamy δ(q, a, X), to δ(q, ε, X) = UWAGA: dopuszcza się ewolucję AZS bez pobierania kolejnego symbolu z wejścia (ε-przejścia), ale dalej musi to być jedyne dozwolone przejście dla danych parametrów. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 153 / 289

Automaty ze stosem Aut [6.4] Podklasa DAZS ma przydatne właściwości: akceptuje wszystkie języki regularne......chociaż nie wszystkie języki akceptowane przez DAZS są regularne jeśli DAZS P akceptuje język L = N(P ), to język ten ma jednoznaczną gramatykę bezkontekstową jeśli DAZS P akceptuje język L = L(P ), to język ten ma jednoznaczną gramatykę bezkontekstową Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 154 / 289

Zmienne i typy danych Zmienne i typy danych Jedną z podstawowywch funkcji języka programowania jest możliwość definiowania i operowania na zmiennych oraz typach danych. Zmienne charakteryzowane są przez 6 atrybutów. Są to: nazwa adres wartość typ okres życia zakres widoczności Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 155 / 289

Zmienne i typy danych Nazwa zmiennej etykieta używana przez programistę, aby odwołać się do komórki pamięci przechowującej dane nie ma znaczenia dla programu po kompilacji różne konwencje w różnych j.programowania, zazwyczaj dozwolone są litery, cyfry i znak podkreślenia fortran77: zmienne o etykietach 6-znakowych pisanych wielkimi literami fortran77: pierwsza litera etykiety zmiennej określała jej domyślny typ (A-H, O-Z REAL, I-N INTEGER) C: zaczął rozróżniać wielkie i małe litery ada (2005): dopuszcza wszystkie znaki unicode prolog: etykieta zmiennej zaczyna się wielką literą, stałej małą nie każda zmienna ma nazwę, np. w C++ deklarując przydział pamięci poleceniem new nazwę nadajemy wskaźnikowi, a nie zmiennej Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 156 / 289

Zmienne i typy danych Adres adres komórki pamięci przechowującej początek wartości zmiennej długość danych zależy od typu zmiennej adres jest unikatowy, w odróżnieniu od nazw zmiennych, które mogą się powtarzać w obrębie podprogramów danego programu jako zmienne lokalne adres nazywany bywa l-wartością, bo jest to wartość występująca po lewej stronie instrukcji przypisania: (adres) == (wartość) Aliasowanie to podpięcie pod jeden adres kilku wskaźników, np. 1 int x, *p, *q; p = &x; q = &x; Prowadzi to do dwóch różnych etykiet dla tej samej wartości (de facto dla tej samej zmiennej) i raczej powinno być unikane ze względów praktycznych (trudno analizować tak napisany kod, łatwo o błędy). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 157 / 289

Zmienne i typy danych Wartość Typ dane reprezentowane przez zmienną zawartość komórek pamięci przypisanych zmiennej zwana też r-wartością, bo występuje po lewej stronie instrukcji przypisania: (adres) == (wartość) technicznie rzecz biorąc, na poziomie maszynowym, typ narzuca ilość pamięci rezerwowanej dla danej zmiennej od strony semantycznej, typ określa interpretację danych bitowych zawartych w komórkach pamięci (znaki ascii, liczby...) ich strukturę (pojedyncze znaki, łańcuchy, tablice, rekordy...) zbiór operacji, jakim dana zmienna może być poddana Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 158 / 289

Zmienne i typy danych Wiązania występują na każdym poziomie projektowanie języka programowania (operatory wbudowane, np. + jako dodawanie liczb) implementacja kompilatora (real zgodny z długością słowa dostępnemu na konkretnej architekturze) kompilacja (związanie zmiennej z zadeklarowanym dla niej typem) wykonanie programu (związanie zmiennej statycznej z adresem pamięci, zmiennej lokalnej z adresem na stosie) Wiązanie dzielimy na statyczne dokonywane są przed wykonaniem programu i są niezmienne (są ustalane w trakcie kompilacji) dynamiczne dokonywane są podczas działania programu i mogą ulegać zmianom sprzętowe niewidoczne i niedostępne dla programisty (wiązanie statyczne może odpowiadać różnym komórkom pamięci fizycznej, a szczególnie wirtualnej, jeśli system operacyjny dokonał takiej reorganizacji) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 159 / 289

Zmienne i typy danych Wiązanie typu polega na przypisaniu zmiennej typu. Określenie typu dla zmiennej może się odbywać na kilka sposobów jawnie poprzez deklarację programisty niejawnie poprzez konwencję użytej nazwy, domyślne zachowanie kompilatora wnioskując z kontekstu statycznie lub dynamicznie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 160 / 289

Zmienne i typy danych Istnieją języki (np. ruby), które przypisują typ zmiennej na bieżąco, w miarę potrzeby. 1 irb ( main ):001:0 > x = 1000 ; puts ( x) ; x. class 2 1000 3 => Fixnum 4 irb ( main ):002:0 > x = x *100000 ; puts ( x) ; x. class 5 100000000 6 => Fixnum 7 irb ( main ):003:0 > x = x *100000 ; puts ( x) ; x. class 8 10000000000000 9 => Bignum 10 irb ( main ):004:0 > x = x *100000 ; puts ( x) ; x. class 11 1000000000000000000 12 => Bignum 13 irb ( main ):005:0 > x = x /10000000000 ; puts ( x) ; x. class 14 100000000 15 => Fixnum 16 irb ( main ):007:0 > x = x /100000 ; puts ( x) ; x. class 17 1000 18 => Fixnum Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 161 / 289

Zmienne i typy danych UWAGA: Języki programowania określa się jako typowane statycznie/dynamicznie, silnie/słabo. Jaka jest różnica? Typowanie dynamiczne vs. statyczne Język typowany statycznie ma typy zmiennych ustalane podczas kompilacji. Te typy się nie mogą zmieniać. Język typowany dynamicznie ma typy zmiennych ustalane w w trakcie wykonywania programu. Te typy mogą się zmieniać. W praktyce takiego programu nie sposób skompilować, trzeba go interpretować w jakiś sposób. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 162 / 289

Zmienne i typy danych Typowanie silne vs. słabe Język typowany silnie nie pozwala na mieszanie typów w pojedynczych wyrażeniach i nie zgaduje, co autor miał na myśli. 1 y = "3" 2 x = y + 1 3 --> ERROR Język typowany słabo stara się dopasować operacje do użytych typów i mimo niespójności deklaracji zaproponować jakiś wynik 1 y = "3" 2 x = y + 1 3 --> (x = "31") LUB (x = 4) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 163 / 289

Zmienne i typy danych Język może również nie być wcale typowany. Nie istnieje w nim wtedy pojęcie typu, a dokładniej to wszystko jest jednego i tego samego typu. Przykład: asembler (bit pattern), tk (text), MatLab core (complex-valued matrix). JĘZYK statycznie dynamicznie słabo silnie Basic x x C x x C++ x x C# x x Delphi x x Fortran x x Haskell x x Java x x Javascript x x Lisp x x Pascal x x Python x (z wyjątkami) Ruby x x Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 164 / 289

Zmienne i typy danych Wiele języków nie daje się tak prosto zakwalifikować. Powstają określenia takie jak: hybrydowe, głównie silne, nie silne itp. Takim językiem jest np. prolog, co do którego klasyfikacji trwają debaty. Duck typing typowanie kacze polega na zastosowaniu zasady: Jeśli chodzi jak kaczka i kwacze jak kaczka, to musi być kaczka. Jest to rodzaj typowania słabego. Rozpoznanie typu następuje poprzez analizę jego właściwości (np. metody obiektu), a nie poprzez deklarację. Na tej zasadzie działa Ruby, Python i inne. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 165 / 289

Zmienne i typy danych Okres życia zmiennej to czas między alokacją (przydzieleniem) a dealokacją (zwolnieniem) pamięci dla tej zmiennej. zmienne statyczne wiązane z pamięcią przed rozpoczęciem wykonania programu, dealokacja następuje w momencie kończenia pracy programu zmienne dynamiczne na stosie alokacja (na stosie) w momencie dotarcia przez program podczas działania do deklaracji takiej zmiennej, dealokacja w momencie zamknięcia bloku programu, w której występowała jej deklaracja zmienne dynamiczne jawne na stercie alokacja i dealokacja jawnie w programie, odwołanie poprzez wskaźniki a nie nazwy zmienne dynamiczne niejawne na stercie alokacja i dealokacja nie są jawnie zadeklarowane w programie, proces odbywa się automatycznie bez kontroli ze strony programisty (np. tworzenie tablic) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 166 / 289

Zmienne i typy danych Zakres widoczności zmiennej określa bloki programu, w których zmienna jest widoczna (można się do niej odwołać) zmienne mogą być globalne lub lokalne języki mogą sprawdzać zakres widoczności zmiennych w sposób statyczny i w sposób dynamiczny Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 167 / 289

Zmienne i typy danych Statyczny zakres widoczności Kompilator napotyka odwołanie do zmiennej. Poszukiwania jej deklaracji zaczyna od bieżącej jednostki programu w dalszej kolejności przeszukuje jednostkę okalającą później kolejną aż do jednostki głównej (zmienne globalne) najbliższa strukturalnie deklaracja przesłania dalsze niektóre języki dają możliwość odwołania się do dalszych instancji zmiennej (np. C++ klasa::zmienna, Ada jednostka.zmienna) niektóre języki dają możliwość deklarowania zmiennej lokalnej w obrębie bloku (np. pętli for); są to wtedy zmienne dynamiczne na stosie W zakresie statycznym (leksykalnym) istotne jest miejsce zadeklarowania zmiennej. Ważna w takim razie jest struktura programu. Jest to dominująca obecnie forma ustalania zakresu widoczności zmiennych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 168 / 289

Zmienne i typy danych Przykład zakres statyczny: 1 int f () { 2 int a 3 a =3.0 4 int g () { 5 print (a) 6 } 7 print (a) 8 } 9 10 a =2.0 11 write "g(a)=", g() 12 write "f(a)=", f() 13 14 >>> 15 g(a )=3.0 16 f(a )=3.0 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 169 / 289

Zmienne i typy danych Dynamiczny zakres widoczności Kompilator napotyka odwołanie do zmiennej. Poszukiwania jej deklaracji zaczyna od bieżącego podprogramu w dalszej kolejności przeszukuje podprogram, z którego nastąpiło wywołanie bieżącego podprogramu później poprzedni aż do jednostki głównej (zmienne globalne) najbliższa czasowo deklaracja przesłania dalsze W zakresie dynamicznym istotna jest kolejność wywołań w programach i podprogramach. Ważny w takim razie jest chronologiczny układ programu. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 170 / 289

Zmienne i typy danych Przykład zakres dynamiczny: funkcja g jest zadeklarowana wewnątrz f, ale wywoływana z poziomu funkcji głównej 1 int f () { 2 int a 3 a =3.0 4 int g () { 5 print (a) 6 } 7 print (a) 8 } 9 10 a =2.0 11 write "g(a)=", g() 12 write "f(a)=", f() 13 14 >>> 15 g(a )=2.0 16 f(a )=3.0 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 171 / 289

Programowanie funkcyjne Rachunek λ HASKELL deklaratywne programowanie funkcyjne Dodatkowa literatura: Paweł Urzyczyn (UW), materiały do wykładu Rachunek Lambda, www.mimuw.edu.pl/~urzy/lambda/erlambda.pdf Małgorzata Moczurad, Włodzimierz Moczurad (UJ), materiały do wykładu Paradygmaty programowania, wykład 8, wazniak.mimuw.edu.pl Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 172 / 289

Programowanie funkcyjne Rachunek λ Rachunek lambda Opracowany w latach 30 XX wieku niezależnie przez Alonzo Church a i Haskella Curry. Rachunek λ to reguły składania i wykonywania obliczeń, w których podstawowym elementem składowym jest funkcja. Dwa główne podejścia: funkcje jako grafy odwzorowanie wejścia na wyjście (podejście nowoczesne) dwie funkcje są tożsame, jeśli dla danego wejścia dają równe sobie wyjścia funkcje jako reguły dane najczęściej wzorem dwie funkcje są tożsame, jeśli wykonują na wejściu równoważne sobie operacje (co skutkuje równym sobie wynikom, ale teraz nie tylko wynik, ale sposób jego otrzymania jest brany pod uwagę) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 173 / 289

Zapis funkcji x 2 Programowanie funkcyjne Rachunek λ f : x x 2 f(x) = x 2 λx.x 2 λx.xx funkcja jako przyporządkowanie funkcja jako reguła funkcja w rachunku λ funkcja w rachunku λ W zapisie funkcyjnym stosuje się wiele uproszczeń notacji: opuszczane są wszystkie niepotrzebne nawiasy opuszcza się kropkę tam, gdzie można λxm (nie będę z tego korzystał) zakłada się, że kropka sięga najdalej na prawo jak to ma tylko sens funkcję kilku zmiennych λx(λy(...(λzm)...)) zapisuje się krócej jako λxy...z.m dla złożenia kilku wyrażeń M N K zakłada się łączność lewostronną (MN)K Przykład: złożenie (f f) funkcji kwadratowych f(x) = x 2 dla x = 5 ((λf.λx.f(f(x)))(λy.y 2 ))(5) = 625 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 174 / 289

Programowanie funkcyjne Rachunek λ Napisy w rachunku λ to termy należące do zbioru Λ. Zbiór ten konstruuje się z pewnego przeliczalnego zbioru zmiennych V ar jako zbiór słów nad alfabetem Σ = V ar {(, ), λ} Mamy następujące typy termów zmienna x V ar x Λ lambda-abstrakcja (λx.m) Λ dla dowolnych M Λ i x V ar aplikacja (MN) Λ dla dowolnych M, N Λ To samo w notacji BNF (Backus Naur Form) Λ : M, N ::= x (λx.m) (MN) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 175 / 289

Programowanie funkcyjne Rachunek λ Zmienne mogą być związane (lokalne) lub wolne (globalne). Zbiór zmiennych wolnych (free variables) w danym termie definiuje się następująco: FV(x) = {x} FV(MN) = FV(M) FV(N) FV(λx.M) = FV(M)\{x} Zmienne stają się związane za sprawą operatora lambda-abstrakcji λ. W wyrażeniu λx.m zmienna x, która najpewniej występuje w termie M, staje się zmienną związaną (lokalną, ograniczoną do tego termu). Etykieta zmiennej związanej jest nieistotna i można ją zmienić na inną, nie występującą w danym termie. Oznacza to, że niektóre termy są sobie równoważne. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 176 / 289

Programowanie funkcyjne Rachunek λ Reguły podstawiania (α-konwersja) x[x := N] = N y[x := N] = y (y x) (P Q)[x := N] = P [x := N]Q[x := N] (λx.p )[x := N] = λn.(p [x := N]) (λy.p )[x := N] = λy.(p [x := N]) (y x, y FV(N)) Podstawianie umożliwia upraszczanie wyrażeń i znajdowanie termów sobie równoważnych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 177 / 289

Programowanie funkcyjne Rachunek λ Twierdzenie o równoważności (wer.1) Termy powiązane ze sobą za pomocą relacji podstawiania są sobie równoważne (α-równoważne, podlegają α-konwersji). (wer.2) Relacja α-konwersji między termami jest równoważna stwierdzeniu, że są one reprezentowane przez taki sam λ-graf. (wer.3) Relacja α-konwersji między termami to najmniejsza relacja taka, że dla wszystkich termów M i zmiennych y nie występujących w M zachodzi λx.m = α λy.(m[x := y]) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 178 / 289

Programowanie funkcyjne Rachunek λ β-redukcja Relacja β-redukcji to najmniejsza relacja w zbiorze termów, spełniająca warunki (1) (λx.m)n β M[x := N] (2) jeśli M β M, to MZ β M Z, ZM β ZM, λx.m β λx.m Definicja Term postaci (λx.m)n nazywany jest β-redeksem. Definicja Jeśli w termie M nie występuje żaden β-redeks, to dla żadnego N nie zachodzi M β N (czyli term nie podlega β-redukcji). Mówi się, że takie termy są w postaci normalnej. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 179 / 289

Programowanie funkcyjne Rachunek λ Podsumowanie: α-konwersja to zamiana etykiet zmiennych związanych; równoważność w tym sensie oznacza termy, które można przeprowadzić jeden w drugi na drodze skończonej liczby zmian nazw β-redukcja to obliczenia wykonywane przez funkcję na podanym argumencie; równoważność termów w tym sensie oznacza, że można przeprowadzić jeden w drugi na drodze skończonej liczby operacji obliczenia wartości funkcji Przykład: Znaleźć wyrażenie równoważne do (λxy.xy)y 1 zmienna y występuje raz w postaci związanej i raz w postaci wolnej, co powoduje problem z interpretacją notacji; stosujemy α-konwersję, aby to naprawić (λxy.xy)y = (λxz.xz)y 2 teraz można obliczyć wartość tego wyrażenia, stosując reguły β-konwersji (λx.p )Q β P [x := Q] (λxz.xz)y = (λx.(λz.xz))y β (λz.xz)[x := y] = λz.yz Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 180 / 289

Programowanie funkcyjne Rachunek λ Twierdzenie Churcha Rossera (właściwość rombu) Podstawowe twierdzenie rachunku lambda. Jeśli term M poprzez pewne ciągi β-redukcji (oznaczane przez β ) redukuje się niezależnie do dwóch różnych termów P i Q, to istnieje term M, do którego można zredukować termy P i Q. M P β β β Q β właściwość rombu nie zachodzi dla β, tylko dla β interpretacja: jeśli istnieje postać normalna, to jest ona unikalna z dokładnością do α-konwersji M Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 181 / 289

Programowanie funkcyjne Rachunek λ W termach złożonych (więcej niż jeden β-redeks), β-redukcje można wykonywać w różnej kolejności, otrzymując różne wyniki. Strategia redukcji lewostronnej (redukujemy pierwszy redeks od lewej) jest strategią normalizującą, czyli prowadzi do postaci normalnej, o ile taka istnieje. Twierdzenie o standaryzacji Wyprowadzenie M 0 M 1... M n jest standardowe, jeżeli do dowolnego 0 i (n 2), w kroku M i M i+1 redukowany jest redeks położony nie dalej od początku termu niż redeks, który będzie redukowany w kroku następnym M i+1 M i+2. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 182 / 289

Programowanie funkcyjne Rachunek λ Przykład: redukcja lewostronna, standardowa (λx.xx)((λzy.z(zy))(λu.u)(λw.w)) = (λx.xx)((λz.(λy.z(zy)))(λu.u)(λw.w)) β β β β β β (λx.xx)((λy.z(zy))[z := (λu.u)](λw.w)) (λx.xx)((λy.(λu.u)((λu.u)y))(λw.w)) (λx.xx)(((λu.u)((λu.u)y))[y := (λw.w)]) (λx.xx)((λu.u)((λu.u)(λw.w))) (λx.xx)((λu.u)(λw.w)) (λx.xx)(λw.w) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 183 / 289

Programowanie funkcyjne Rachunek λ Przykład: term T T, gdzie T = λx.xx nie posiada postaci normalnej T T = (λx.xx)t β T T Przykład: różne strategie redukcji prowadzą do różnych wyników (λx.y)(t T ) β (λx.y)(t T ) β... redukując T T β y[x := T T ] = y redukując lewostronnie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 184 / 289

Programowanie funkcyjne Rachunek λ Związek rachunku λ z programowaniem Wyrażenia Boole a Przyjmuje się definicje true λxy.x false λxy.y Pozwalają one na zbudowanie instrukcji warunkowej if-then-else (IFE) IFE λx.x tak, że if (warunek) then P else Q zapisujemy jako IFE true P Q β P IFE false P Q β Q Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 185 / 289

Programowanie funkcyjne Rachunek λ Sprawdzenie IFE true P Q β (λx.x)(λz.(λy.z))p Q β (λx.x)(λy.p )Q β (λx.x)(p [y := Q]) = (λx.x)p β P IFE false P Q β (λx.x)(λz.(λy.y))p Q β (λx.x)(λy.y[z := P ])Q = (λx.x)(λy.y)q β β (λx.x)q Q Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 186 / 289

Programowanie funkcyjne Rachunek λ Możliwe jest zdefiniowanie operacji logicznych, np. AND λxy.(xy false) : AND true true β true AND true false β false AND false true β false AND false false β false Alternatywną definicją jest AND λxy.yxy W podobny sposób można skonstruować definicje pozostałych operacji logicznych. Podsumowując: AND λxy.xy false OR λxy.x true y NEG λx.x false true Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 187 / 289

Programowanie funkcyjne Rachunek λ Liczebniki Churcha. W notacji rachunku λ liczby są reprezentowane przez abstrakcje λf x.x = 0 λfx.fx = 1 λfx.f(fx) = 2 λfx.f(f(fx)) = 3 λfx.f n x = n Interpretacja: liczba n to n-krotnie zastosowana operacja (funkcja) następnik do liczby zero. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 188 / 289

Programowanie funkcyjne Rachunek λ Przykład: f(x) = x 2, x = 2 (λx.xx)2 β 2 2 = (λf.(λy.f(fy)))2 β β β β λy.2(2y) = λy.(λf.(λx.f(fx))(2y)) λyx.2y(2yx) = λyx.(λf.(λz.f(fz)))y(2yx) λyx.(λz.y(yz))(2yx)) = λyx.y(y2yx) λyx.y(y(λf.(λz.f(fz)))yx) = λyx.y(y(λz.y(yz))x) β λyx.y(y(y(yx))) = λfx.f 4 x = 4 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 189 / 289

Programowanie funkcyjne Rachunek λ Standardowe funkcje wykorzystywane w rachunku lambda nazwa symbol definicja działanie następnik succ λnfx.f(nfx) succ a β a + 1 dodaj add λmnfx.mf(nfx) add a b β a + b pomnóż mult λmnfx.m(nf)x mult a b β a b czy zero? zero λm.m(λy.false)true zero(0) β true zero(n) β false Istnieją alternatywne postaci tych definicji w literaturze. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 190 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Podstawowa składnia języka haskell Haskell jest językiem wysokopoziomowym funkcyjnym silnie typowanym z typami wnioskowanymi leniwym Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 191 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Programowanie leniwe i gorliwe Funkcje leniwe program wykonuje tylko te operacje, które w danej chwili są potrzebne. Funkcje gorliwe zawsze wykonywana jest cała funkcja. Leniwe obliczanie funkcji Wyrażenia nie są obliczane w momencie wiązania ich do zmiennej, ale dopiero, gdy napotkane zostanie odwołanie do konkretnego wyniku. Zalety: wzrost wydajności poprzez oszczędne wykonywanie instrukcji można posługiwać się niewyliczalnymi wyrażeniami, np. nieskończonymi pętlami, nieskończonymi listami etc. Wady: problem z występowaniem skutków ubocznych obliczeń (I/O, wyjątki, kontrola błędów etc.) problem z oszacowaniem wymaganej pamięci dla wykonania programu problem z kontrolą kolejności wykonywanych operacji Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 192 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Tablicowanie wartości funkcji Aby usprawnić korzystanie z obliczeń leniwych korzysta się z tablic, przechowujących wyniki już obliczonych wartości danego wyrażenia dla pewnych argumentów. Przy kolejnym wywołaniu funkcji sprawdza się, czy tablica nie przechowuje interesującego wyniku, aby uniknąć jego powtórnego wyliczania ( memoization). Przykład leniwego obliczania wartości funkcji: 1 takewhile ( <50) ( map kw [0..]) where kw x = x*x Prelude> let kw x = x*x Prelude> takewhile (<50) (map kw [0..]) [0,1,4,9,16,25,36,49] Definicja nieskończonej listy [1,1,1,...] 1 x = 1: x in x Prelude> let x = 1:x in x 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,Interrupted. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 193 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Pytania: po co nam były wszystkie te teoretyczne wywody o gramatykach, skoro koniec końców i tak chodzi o napisanie kodu? nie wystarczy wziąć manual do danego języka i zapoznać się z jego składnią? Dokument Haskell Report 2010 www.haskell.org/definition/haskell2010.pdf na stronach 7 8 i dalszych podaje składnię leksykalną haskella. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 194 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 195 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 196 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 197 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 198 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 199 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 200 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 201 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Konwencje w skrócie i bardziej po ludzku: argumenty funkcji podaje się bez nawiasów, chyba, że są to krotki nazwy funkcji nie zaczynają się wielkimi literami apostrof po nazwie funkcji zwyczajowo oznacza funkcję gorliwą (strict function), w odróżnieniu od szybszej funkcji leniwej (lazy function), ale nie ma znaczenia syntaktycznego bloki programu oddzielane są pustą linią kontynuacje deklaracji mają wcięcia (najlepiej, aby były one zgodne z początkiem deklaracji) deklaracje zagnieżdżone powinny mieć coraz głębsze wcięcia linia nie powinna przekraczać 78 znaków (niepolecane) bloki można też ograniczać nawiasami {... }, zaś znaki końca linii zastępować średnikiem Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 202 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Formalna struktura kodu w haskellu moduł moduł składa się z deklaracji, takich jak: wartości (values), typy danych (datatypes), klasy typów (type classes), informacje naprawcze (fixity information) na deklaracje składają się wyrażenia wyrażenia tworzone są na podstawie składni leksykalnej języka (na tym poziomie mówimy o pliku tekstowym zawierającym kod programu) Wyrażenie (expression) jest ewaluowane do wartości z przypisanym statycznie typem. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 203 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Układ kodu w haskellu haskell bazuje na blokach kodu, a definicje produkcji gramatyki tego języka zawierają często średniki i nawiasy {..;..;.. }; ograniczające te bloki taki styl kodowania nazywany jest layout-insensitive i jest użyteczny, gdy kod haskella ma być generowany przez inny automat/program ze względów praktycznych nawiasy ograniczające bloki mogą być pominięte, jednak wtedy konieczne jest zapisywanie komend w nowych liniach i odpowiednie używanie narastających wcięć taki styl kodowania nazywany jest layout-sensitive i jest powszechnie używany w większości zastosowań Bloki są ważne ze względu na zakres widoczności (scope) funkcji i zmiennych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 204 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Mniej formalnie o strukturze programu: deklaracja funkcji main deklaracje innych funkcji komentarze zaczynają się od -- w dowolnym miejscu linii, komentarze zagnieżdżone są ograniczone {-... -} Przykładowy program 1 main :: IO () 2 main = do 3 let t=[2,1,2,3,4,5,6,7,8,9,10] 4 print ( parzyste t) 5 print ( parzyste []) 6 7 parzyste :: [ Integer ] -> [ Integer ] 8 parzyste [] = [] 9 parzyste ( g: o) = if even g then g: parzyste o 10 else parzyste o Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 205 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Listy uszeregowane elementy jednego typu mogą być nieskończone i zmieniać swoją długość a=[1..10] a=[ a.. z ] a=[2,4..20] Operator : dodaje element na początek listy 1:2:3:[] pozwala też rozdzielić listę na jej głowę i ogon Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 206 / 289

Operator ++ scala listy Programowanie funkcyjne 7J [8.1] [8.2] "Hello" ++ " " ++ "world!" Operator!! pobiera element listy (numeracja od 0) "Haskell!"!! 3 Wbudowane funkcje operacji na listach to m.in. head a tail a last a init a length a Prelude> let a=["a","b","c","d","e"] Prelude> head a "A" Prelude> tail a ["B","C","D","E"] Prelude> last a "E" Prelude> init a ["A","B","C","D"] Prelude> length a 5 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 207 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Krotki mają z góry ustaloną długość mogą zawierać elementy różnych typów służą np. do opisu funkcji wieloargumentowych definicja w nawiasach okrągłych, np. (a,b,c) Zbiory i przedziały Oprócz list i krotek możliwe jest zdefiniowanie skończonego lub nieskończonego zbioru elementów, generowanych za pomocą danej reguły. Elementy te są przechowywane w postaci listy. Przykład: zbiór liczb parzystych mniejszych od 31 [x*2 x<-[1..15]] Instrukcja warunkowa if..then..else.. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 208 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Przykładowa definicja funkcji Funkcja signum Wersja 1: 1 gdy x < 0 sgn(x) = 0 gdy x = 0 +1 gdy x > 0 1 sgn1 :: Integer - > Integer 2 sgn1 n = if n >0 then 1 else if n ==0 then 0 else -1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 209 / 289

Programowanie funkcyjne 7J [8.1] [8.2] Wersja 2: 1 sgn2 :: Integer - > Integer 2 sgn2 n 3 n >0 = 1 4 n ==0 = 0 5 n <0 = -1 Wersja 3: 1 sgn3 :: Integer - > Integer 2 sgn3 0 = 0 3 sgn3 n 4 n >0 = 1 5 n <0 = -1 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 210 / 289

Programowanie funkcyjne 7J [8.3] Funkcje anonimowe. Funkcje anonimowe nie mają nadawanej nazwy. Składnia: 1 Prelude > (\x -> x*x) 2 2 4 3 Prelude > (\x -> x*x) ((\ y -> y +4) 2) 4 36 przypomina (celowo) zapis z rachunku λ. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 211 / 289

Programowanie funkcyjne 7J [8.3] W ten sposób można np. zweryfikować instrukcję warunkową IFE 1 Prelude > let true = \ x y - > x 2 Prelude > let false = \ x y - > y 3 Prelude > let ife = \ x - > x 4 Prelude > ife true 4 6 5 4 6 Prelude > ife false 4 6 7 6 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 212 / 289

Programowanie funkcyjne 7J [8.3] Definicja typu Church zamieniającego liczebnik Churcha na liczbę i odwrotnie 1 type Church a = (a -> a) -> a -> a 2 3 church :: Integer - > Church Integer 4 church 0 = \f x -> x 5 church n = \f x -> f ( church (n -1) f x) 6 7 unchurch :: Church Integer - > Integer 8 unchurch cn = cn (+ 1) 0 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 213 / 289

Programowanie funkcyjne 7J [8.3] Po załadowaniu definicji do konsoli ghci: 1 *Main > unchurch (\f x -> f(f(f(f x )))) 2 4 3 * Main > church 5 4 5 <interactive >:4:1: 6 No instance for ( Show ( Church Integer )) 7 arising from a use of print 8 Possible fix : 9 add an instance declaration 10 for ( Show ( Church Integer )) 11 In a stmt of an interactive 12 GHCi command : print it możliwe jest tłumaczenie liczebników Churcha na liczby i na odwrót. Kłopot jest z wyświetlaniem liczebników w postaci funkcji anonimowych. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 214 / 289

Programowanie funkcyjne 7J [8.3] Można dołożyć do kodu moduł 1 import Text. Show. Functions i wtedy ghci nie zgłasza błędu (nowy zapis: $ oznacza nawias otwierający) 1 * Main > church 5 2 < function > 3 *Main > church $ unchurch (\f x -> f(f(f(f x )))) 4 < function > 5 * Main > unchurch $ church 4 6 4 Dlaczego nie jest wyświetlana funkcja? Funkcja \f x = f(f x) jest tożsama \g y = g(g y) (α-konwersja). Którą z nich miałby haskell wyświetlić? Złamana zostałaby reguła, że dane wejście funkcja ewaluuje do jednoznacznie określonego wyjścia. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 215 / 289

Programowanie funkcyjne 7J [8.3] Typy polimorficzne Typy w Haskellu określają (przeciw)dziedziny funkcji: Int, Integer krótki, długi integer Float, Double pojedynczej i podwójnej precyzji liczba zmiennoprzecinkowa Bool Char Typy są pogrupowane w klasy typów (type classes). Klasy te określają wspólne właściwości typów należących do danej klasy niektóre zawierają się w innych pozwalają na pisanie definicji funkcji dla całej klasy typów, a nie dla poszczególnych typów zmiennych, tzw. funkcje polimorficzne Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 216 / 289

Programowanie funkcyjne 7J [8.3] Funkcja zwykła 1 plusdwa :: Integer - > Integer 2 plusdwa x = x +2 jest dobrze określona tylko dla zmiennej typu Integer i żadnej innej. Haskell ma ścisłą kontrolę typów. Zamiast pisać nowe funkcje dla typów Int, Float i Double wystarczy zmienić nagłówek 1 plusdwa :: ( Num a) => a -> a 2 plusdwa x = x +2 Klasa typów Num obejmuje wszystkie typy numeryczne. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 217 / 289

Programowanie funkcyjne 7J [8.3] Klasy typów w Haskellu Definicja klasy zazwyczaj określa funkcję (operator), którą można zastosować do typów należących do danej klasy. Klasa Definicja Eq równość elementów ==, /= Ord porządkowanie elementów <, <=, >=, >; podklasa Eq Ordering porządkowanie elementów GT, EQ, LT Show elementy reprezentowane przez ciągi znakowe (funkcja show) Read elementy reprezentowane przez ciągi znakowe (funkcja read) Enum wyliczenia (np. listy); zawiera Bool, Char, Ordering, Num Bounded wartości elementów ograniczone Num typy numeryczne; muszą należeć do Eq i Show Integral Int i Integer Floating Float i Double Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 218 / 289

Programowanie funkcyjne 7J [8.3] Operatory w Haskellu też są funkcjami. Np. operator binarny, który dwa argumenty typu a zamienia na inny element typu a jest funkcją a -> a -> a. Aby zaznaczyć alternatywny sposób wywołania funkcji (prefix i infix) stosuje się nawiasy i odwrotne cudzysłowy. jeśli w definicji nazwa funkcji ujęta jest w nawiasy, to domyślnie jest to definicja infiksowa przy wywołaniu prefiksowym te nawiasy muszą pozostać przy wywołaniu infiksowym nawiasy się pomija jeśli w definicji nazwa funkcji nie jest ujęta w nawiasy, to domyślnie jest to definicja prefiksowa przy wywołaniu prefiksowym podaje się nazwę funkcji przy wywołaniu infiksowym nazwę funkcji otacza się odwrotnymi cudzysłowami Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 219 / 289

Programowanie funkcyjne 7J [8.3] (Przykłady z wykładu Moczurad i Moczurad) Przykład 1. Definicja i poprawne wywołania 1 (%%%) :: Integer -> Integer -> Integer 2 (%%%) x y = x + 5 * y 3 %%% 7 (%%%) 3 7 Przykład 2. Definicja i poprawne wywołania 1 abc :: Integer - > Integer - > Integer 2 abc x y = 2 * x + y * y 3 abc 7 abc 3 7 Przykład 3. Funkcja dwuargumentowa jako jednoargumentowa ( div 3) ((+4) 8) (( div 3).(+4)) 8 (8+4) div 3 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 220 / 289

Programowanie funkcyjne 7J [8.3] Definicje rekurencyjne Przykład 1.1 Definicja funkcji maksimum dla list 1 maximum :: ( Ord a) => [a] -> a 2 maximum [] = error " Pusta lista " 3 maximum [x] = x 4 maximum (x: ogon ) 5 x > maxogon = x 6 otherwise = maxogon 7 where maxogon = maximum ogon Przykład 1.2 Definicja funkcji maksimum dla list; wykorzystuje wbudowaną funkcję max 1 maximum :: ( Ord a) => [a] -> a 2 maximum [] = error " Pusta lista " 3 maximum [x] = x 4 maximum ( x: ogon ) = max x ( maximum ogon ) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 221 / 289

Programowanie funkcyjne 7J [8.3] Przykład 2 Funkcja odwracająca listę 1 reverse :: [a] -> [a] 2 reverse [] = [] 3 reverse ( x: ogon ) = reverse ogon ++ [ x] odwrotnością listy pustej jest lista pusta odwrotną listę można skonstruować przenosząc pierwszy element na koniec, pod warunkiem, że reszta jest już odwróconą listą tak więc wywołujemy reverse na coraz krótszych listach (punktem zwrotnym rekurencji jest lista pusta), trzymając elementy, które będą dopisywane na końcu w pamięci Schematycznie: r[1,2,3,4] = r[2,3,4]+[1] = r[3,4]+[2]+[1] = r[4]+[3]+[2]+[1] = r[]+[4]+[3]+[2]+[1] = [4]+[3]+[2]+[1] = [4,3]+[2]+[1] = [4,3,2]+[1] = [4,3,2,1] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 222 / 289

Programowanie funkcyjne 7J [8.4] Monady i składanie funkcji Ograniczenia języko czysto funkcyjnego wszystko jest funkcją, więc każde polecenie musi zwrócić wartość funkcja zawsze zwraca tę samą wartość dla tego samego argumentu funkcja zawsze zwraca jedną wartość (jest jednoznacznie określona) funkcje można składać f(g(x)) = (f g)(x) 1 Prelude > let f x = x +2 2 Prelude > let g x = x -2 3 Prelude > (f.g) 5 4 5 Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 223 / 289

Programowanie funkcyjne 7J [8.4] Złożenie funkcji f.g było możliwe, ponieważ ich typy na to zezwalały 1 Prelude > : t f 2 f :: Num a => a -> a 3 Prelude > : t g 4 g :: Num a => a -> a czyli przeciwdziedzina g była zawarta w dziedzinie f. Ale nie zawsze tak musi być! Problem: efekty uboczne Dla języka funkcyjnego problemem jest sytuacja, w której funkcja ma robić kilka rzeczy, np. obliczać wartość i wypisywać dane na ekran. Czysta funkcja nie może generować efektów ubocznych, więc w takich sytuacjach efekt ten musi być częścią zwracanej wartości. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 224 / 289

Programowanie funkcyjne 7J [8.4] Rozwiązaniem tego i innych problemów z funkcjami są monady. Monada pozwala na zdefiniowanie reguł składania funkcji o częściowo niezgodnych typach reguł przekazywania wartości funkcji na wejście innej funkcji Proszę zapoznać się z artykułami: blog.sigfpe.com/2006/08/ you-could-have-invented-monads-and.html en.wikipedia.org/wiki/monad_(functional_programming) wiki.haskell.org/all_about_monads Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 225 / 289

Programowanie funkcyjne 7J [8.4] Przykład: funkcje liczą i wyświetlają komunikaty 1 f :: Float - > ( Float, String ) 2 f x = (2*x, " Dziala f ") 3 4 g :: Float - > ( Float, String ) 5 g x = (x+1, " Dziala g ") Intuicyjnie, złożenie funkcji f.g powinno działać tak, że zwraca wartość 2*(x+1) i wyświetla napis " Dziala f Dziala g " Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 226 / 289

Programowanie funkcyjne 7J [8.4] Wbudowane złożenie f.g jest niemożliwe ze względu na niezgodność typów 1 Main > (f.g) 3 2 3 <interactive >:7:4: 4 Couldn t match expected type Float 5 with actual type ( Float, String ) 6 Expected type : Float - > Float 7 Actual type : Float - > ( Float, String ) 8 In the second argument of (.), namely g 9 In the expression : f. g Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 227 / 289

Programowanie funkcyjne 7J [8.4] Oczywiście można napisać swoją funkcję, która będzie realizowała złożenie f.g 1 fg :: Float - > ( Float, String ) 2 fg x = ( fst (f ( fst (g x))), snd (f x) ++ snd (g x)) ale jest ona specyficzna dla naszego konkretnego przypadku. Poza tym nie jest ona pomocna, gdyby interesowało nas złożenie g.f mimo, że od strony typów sytuacja jest analogiczna. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 228 / 289

Programowanie funkcyjne 7J [8.4] Rozważmy funkcję bind (2 równoważne zapisy) 1 bind :: ( Float -> ( Float, String )) -> 2 (( Float, String ) -> ( Float, String )) 3 bind f (gx, gs) = let (fx, fs) = f gx 4 in (fx,fs ++ gs) 1 bind :: ( Float -> ( Float, String )) -> 2 (( Float, String ) -> ( Float, String )) 3 bind f (gx, gs) = (fx,fs ++ gs) 4 where ( fx, fs) = f gx Składnia z let... in... oraz where pozwala na łatwe oznaczanie partii kodu nową zmienną lokalną (jak w matematyce) utrzymanie przejrzystości wyrażeń (jak w matematyce) większą elegancję kodu (jak w...?) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 229 / 289

Programowanie funkcyjne 7J [8.4] 1 * Main > bind f ( g 3) 2 (8.0, " Dziala f Dziala g ") 3 * Main > bind g ( f 3) 4 (7.0, " Dziala g Dziala f ") Funkcja bind definiuje ogólny schemat składania funkcji typu Float -> (Float,String) pozwala na złożenie tych funkcji w dowolnej kolejności jest składową monady Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 230 / 289

Programowanie funkcyjne 7J [8.4] Ogólny schemat: powiedzmy, że mamy funkcję ukrywającą strukturę danych Ukryj. Definiujemy nowy typ danych dane ukryte i funkcję ukrywającą 1 data Ukryte a = Ukryj a 2 3 return :: a - > Ukryte a 4 return x = Ukryj x Aby operować na ukrytych danych bez ich odkrywania definiuje się funkcję wiążącą 1 bind :: (a -> Ukryte b) -> ( Ukryte a -> Ukryte b) 2 bind f ( Ukryj x) = f x lub 1 bind :: (a -> Ukryte b) -> ( Ukryte a -> Ukryte b) 2 bind f ( return x) = f x Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 231 / 289

Programowanie funkcyjne 7J [8.4] Wbudowany operator pozwalający na składanie funkcji, czyli pełniący rolę funkcji bind w haskellu, to >>=. bind f (return x) x >>= f Operator >>= jest zdefiniowany inaczej w zależności od typu monady. Najprostsza jego definicja to wartość >>= funkcja w której wartość z lewej strony jest przenoszona jako argument funkcji po prawej stronie i zwracana jest wartość tej funkcji na tym argumencie. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 232 / 289

Programowanie funkcyjne 7J [8.4] W haskellu monady predefiniowane są poprzez odpowiednie klasy. Standardowa klasa Monad jest zdefiniowana następująco: 1 class Monad m where 2 ( > >=) :: m a -> (a -> m b) -> m b 3 return :: a -> m a 4 (>>) :: m a -> m b -> m b 5 fail :: String - > m a 6 7 m >> k = m >>= (\_ -> k) 8 fail k = error k m jest konstruktorem typu monady return jest funkcją tworzącą instancje typu m >>= jest funkcją wiążącą instancje monady z obliczeniami >> operator analogiczny do >>=, ale nie wykorzystujący wyniku poprzedniej operacji fail jest wywoływane, gdy coś się nie powiedzie w konstrukcji złożenia Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 233 / 289

Programowanie funkcyjne 7J [8.4] Funkcje >> i fail nie występują w matematycznej definicji monady, ale są częścią klas z rodziny Monad w haskellu, gdyż ułatwia to programowanie. Części składowe monady muszą spełniać 3 podstawowe prawa: 1 (return x) >>= f == f x 2 m >>= return == m 3 (m >>= f) >>= g == m >>= (\x -> f x >>= g) których interpretacja jest następująca: 1 return jest lewostronnym elementem neutralnym dla funkcji >>= 2 return jest prawostronnym elementem neutralnym dla funkcji >>= 3 rodzaj prawa łączności obowiązującego funkcję >>= Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 234 / 289

Programowanie funkcyjne 7J [8.4] Przykład: mamy do czynienia z obliczeniami, które mogą się nie udać, tj. są sytuacje, w których funkcja nie zwróci wartości. Jest to problematyczne zachowanie w języku funkcyjnym. Wykorzystuje się wtedy monadę opartą o wbudowany konstruktor typu Maybe 1 data Maybe m = Nothing Just m Można zadeklarować Maybe jako składnik klasy Monad 1 instance Monad Maybe where 2 Nothing > >= f = Nothing 3 ( Just x) >>= f = f x 4 return = Just Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 235 / 289

Programowanie funkcyjne 7J [8.4] Przykład c.d. Klonowanie owiec nie każda owca ma dobrze określonego ojca i matkę. 1 type Owca =... 2 3 tata :: Owca - > Maybe Owca 4 tata =... 5 6 mama :: Owca - > Maybe Owca 7 mama =... 8 9 -- ojciec ojca mamy 10 oom :: Owca - > Maybe Owca 11 oom o = ( Just o) > >= mama > >= tata > >= tata Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 236 / 289

Programowanie funkcyjne 7J [8.4] Taka konstrukcja automatycznie będzie poprawnie traktowała sytuację, gdy w ciągu złożenia pojawi się brak wartości którejś funkcji (Nothing). Rozkładając ciąg (Just o) >>= mama >>= tata >>= tata z definicji otrzymamy tata(tata(mama o)) o ile każda z tych funkcji zwróci wartość, w przeciwnym razie wynikiem będzie Nothing. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 237 / 289

Programowanie funkcyjne 7J [8.4] Do czego w praktyce służą monady? funkcje wieloargumentowe są rozbijane na jednoelementowe i sklejane razem, np. plus x y = \x -> (\y -> x+y) listy są tworami o nieustalonej z góry długości, być może puste, więc monada typu List pozwala na definiowanie obliczeń mogących zwracać 0, 1 lub więcej wartości operacje I/O są obsługiwane w haskellu poprzez odpowiednią monadę, ponieważ są to najczęściej efekty uboczne działania funkcji (właściwe obliczenia plus operacja I/O) obsługa błędów Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 238 / 289

Programowanie funkcyjne 7J [8.4] W haskellu istnieje biblioteka wzorców monad (Monad Template Library), która jest włączona do ghc pod wywołaniem Control.Monad. Niektóre z nich to: MONADA DO CZEGO UŻYTECZNA? Identity używana przy konstrukcjach innych monad Maybe obliczenia mogące nie zwrócić wartości Error obliczenia mogące zawieść [] (List) obliczenia o nieokreślonej liczbie wartości IO obliczenia połączone z operacjami I/O State obliczenia przekazujące parametr stanu Reader obliczenia czytające wejście Writer obliczenia produkujące dodatkowy strumień danych Cont obliczenia, które można wstrzymać i wznowić Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 239 / 289

PROLOG 7J [4.1] PROLOG deklaratywne programowanie w logice Dodatkowa literatura: D. Diaz, GNU Prolog Manual, edition 1.44 http://www.gprolog.org/manual/gprolog.html http://www.gprolog.org/manual/gprolog.pdf W.F. Clocksin, C.S. Mellish, Prolog. Programowanie (Helion, druk na żądanie) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 240 / 289

PROLOG 7J [4.1] Prolog oznacza Programming in Logic. Jest to język deklaratywny oparty na wyrażeniach logiki (to nie jest paradygmat imperatywny logiczny!). Opisany w 1972 przez Alain Colmerauer (standardy ISO w 1995 i 2000). Używany w takich dziedzinach jak: relacyjne bazy danych logika matematyczna problemy abstrakcyjne języki naturalne automatyka algebra symboliczna biochemia sztuczna inteligencja Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 241 / 289

PROLOG 7J [4.1] Logika użyta w Prologu jest standardową dwuwartościową logiką matematyczną, opisywaną algebrą Boole a. Podstawowe operacje to I (AND) LUB (OR) NEGACJA (NOT) JEŚLI (IF) zaś wartości logiczne to true i false. Prolog stara się uzgodnić wszystkie zdania w programie do logicznej wartości true. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 242 / 289

PROLOG 7J [4.1] Klauzula Horna jest wyrażeniem logicznym, w którym co najwyżej jeden człon nie jest zanegowany. Ogólna postać: p 1 + p 2 + + p n + q Można je przekształcić do postaci równoważnej, która jest użyteczna w programowaniu ( p 1 + p 2 + p 3 + q) (p 1 + p 2 + p 3 q) W notacji Prologu takie stwierdzenie ma postać 1 q :- p1, p2, p3. co należy rozumieć jako: q jest prawdziwe JEŚLI (p1 I p2 I p3) są prawdziwe Programy w Prologu to niemal wyłącznie zestaw klauzul Horna. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 243 / 289

PROLOG 7J [4.1] Podstawowymi pojęciami w prologu są termy, wiązane uporządkowanymi relacjami. Przykład: termem mogą być jaś, małgosia relacjami mogą być brat, siostra relacje są uporządkowane, co oznacza, że jaś jest w relacji brat z termem małgosia, ale niekoniecznie na odwrót Schemat programu w prologu 1 deklaracja faktów (baza danych termów i relacji) 2 deklaracja reguł dozwolonych do manipulowania faktami 3 deklaracja problemu (zadanie pytania) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 244 / 289

PROLOG 7J [4.2] Przykład bazy wiedzy (bazy danych, faktów): 1 rodzenstwo ( jas, malgosia ). 2 rodzenstwo ( malgosia, jas ). 3 brat (jas, malgosia ). 4 siostra (jas, malgosia ). Uwaga: nie ma potrzeby definiowania, czym są użyte termy jas, brat etc. definicja tworzona poprzez właściwości predykat(argument1,argument2). jest relacją etykiety stałych zaczynają się małą literą definicje relacji kończą się kropką W powyższym przykładzie zdefiniowane zostały trzy predykaty (rodzenstwo/2, brat/2, siostra/2) i dwie stałe (jas, malgosia). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 245 / 289

PROLOG 7J [4.2] predykaty służą do definiowania relacji argumentami predykatów są termy proste (czyli stałe lub zmienne) termy złożone to termy proste połączone funktorami (o znaczeniu logicznego and, or) Relacje definiowane są zupełnie abstrakcyjnie, np. x(a,b), jednak użyte etykiety sugerują przypisywane im znaczenie. Poniższe relacje są dla Prologu takie same : 1 x(a,b). 2 kolor ( czerwony, papryka ). 3 ojciec ( zdzich, eustachy ). 4 wieksze (trzy, cztery ). Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 246 / 289

PROLOG 7J [4.2] każda stała to osobny byt różne stałe reprezentują różne byty...... nawet jeśli wartości przechowywane przez te stałe są takie same! Przykład w fortranie 1 x=2 2 y=2 3 if (x.eq.y) then...! true Przykład w prologu 1?- x=2, y=2, x=y. 2 3 no Tak naprawdę, to już stwierdzenie x=2 daje odpowiedź no w Prologu. Dlaczego? Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 247 / 289

PROLOG 7J [4.2] Zmienna w prologu ma nieco inne właściwości, niż w innych językach programowania: nie jest typowana w momencie inicjalizacji pierwsze wyrażenie, które przypisze zmiennej jakąś wartość, ustala tę wartość raz na zawsze np., wyrażenie typu if X = 2 then... nie podstawi pod zmienną X liczby 2 w fortranie, lecz analogiczne wyrażenie w prologu może to zrobić, jeśli X jest zmienną bez przypisanej wartości ) etykiety zmiennych zaczynają się wielkimi literami, np. X, Kuba... ) Prolog traktuje wyrażenie X=2 jako wyrażenie logiczne, które musi być spełnione, aby można było kontynuować wykonywanie programu. Dlatego też jeśli X jest zmienną nieprzypisaną, takie przypisanie nastąpi i warunek otrzyma logiczną wartość true. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 248 / 289

PROLOG 7J [4.2] Zmiennych w prologu używa się, aby zadawać pytania, jako miejsce do przechowania wyniku działały jako akumulator w algorytmach rekurencyjnych 1 kolor ( czerwony, auto ). 2 kolor ( zielony, ufoludek ). 3 4?- kolor (X, auto ). 5 6 X = czerwony 7 8 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 249 / 289

PROLOG 7J [4.2] Podstawowe funktory logiczne przecinek, AND średnik ; OR dwukropek myślnik : IF Warunki czytane są i wykonywane (interpretowane) od lewej do prawej. Kolejność ma znaczenie, mimo że wiele z nich, z formalnego punktu widzenia, jest połączona funktorami symetrycznymi względem przestawienia argumentów. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 250 / 289

PROLOG 7J [4.2] Przykład: 1 lata ( samolot ). 2 lata ( mucha ). 3 kolor ( czarny, mucha ). 4 kolor ( czarny, samolot ). 5 6?- lata (X), kolor ( czarny,x). 7 X = samolot? a 8 X = mucha 9 no 10 11?- kolor ( czarny,x), lata (X). 12 X = mucha? a 13 X = samolot 14 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 251 / 289

PROLOG 7J [4.2] Wnioski z poprzedniego przykładu: jeśli istnieje więcej niż jedno rozwiązanie, prolog znajdzie je wszystkie kolejność znajdowania rozwiązań zależy m.in. od kolejności instrukcji w programie (są one czytane z góry na dół, od lewej do prawej) znajdowanie różnych rozwiązań jest możliwe, gdyż prolog pracuje na relacjach a nie na funkcjach powrót prologu w strukturach kodu i przyjęcie innej strategii uzgadniania rozwiązania nazywane jest nawracaniem (ang. backtracking) i jest charakterystyczną cechą języków logicznych Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 252 / 289

PROLOG 7J [4.2] Reguły w prologu (fakty) mają postać GŁOWA IF WARUNEK (WARUNKI) 1 siostra ( małgosia,x) :- brat (X, małgosia ), 2 kobieta ( małgosia ). Modus operandi prologu w takim przypadku obejmuje podstaw pod X z głowy reguły stałą występującą w programie sprawdź czy takie X spełnia iloczyn warunków brat/2 i kobieta/1 jeśli tak, spytaj się, czy szukać kolejnych rozwiązań ewentualnie przeszukaj bazę wiedzy i sprawdź kolejne X powtarzaj, aż wyczerpią się możliwości lub użytkownik przerwie Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 253 / 289

PROLOG 7J [4.2] Funktory łączą termy proste tworząc termy złożone. Mogą przyjąć formę: predykatów, operatorów. Stosowana jest notacja: infiksowa: 3*4, 3 pomnoz 4, prefiksowa: * 3 4, *(3,4), pomnoz(3,4). Operatorom przypisuje się różne priorytety wykonywania oraz określa się, czy są one lewo czy prawostronnie łączne. W ten sposób można zaprogramować algebrę, rachunek lambda, gramatyki bezkontekstowe etc. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 254 / 289

PROLOG 7J [4.3] Funktor = oznacza unifikację termów dwa jednakowe termy można zawsze zunifikować 1 A=A. /* yes */ 2 a=a. /* yes */ 3 a=b. /* no */ zmienną nieprzypisaną można zunifikować z innym termem przypisując jej typ i wartość tego termu 1 A=B. /* yes : B zmienna przypisana */ 2 A=b. /* yes */ unifikacja dwóch nieprzypisanych zmiennych jest zawsze możliwa 1 A=B. /* yes : A, B nieprzypisane */ zarówno atomy, jak i liczby, mogą być łatwo porównywane wg standardowych reguł Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 255 / 289

PROLOG 7J [4.3] UWAGA unifikacja nie jest przypisaniem ale...... przypisanie wartości może być efektem ubocznym stosowanej unifikacji wynikiem unifikacji jest zawsze true albo false Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 256 / 289

PROLOG 7J [4.3] Przykłady: 1 data (1, kwie,2014) = data (1, kwie,2014) 2 /* termy zunifikowane */ 3 4 data (1, kwie,2014) = data (D,kwie,2014) 5 /* termy zunifikowane jeśli : 6 - D można przypisać wartość 1 */ 7 8 data (Day1,kwie,2014) = data (D2,kwie,2014) 9 /* termy zunifikowane jeśli : 10 - Day1 i D2 mają tę samą wartość ; 11 - jedna z nich jest nieprzypisana 12 i można ją zunifikować z drugą ; 13 - obie są nieprzypisane ( i takie zostaną, 14 ale będą związane ze sobą ) */ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 257 / 289

PROLOG 7J [4.3] Przykłady: 1?- A=B. 2 B = A 3 yes 4 5?- A=2, A=B. 6 A = 2 7 B = 2 8 (1 ms) yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 258 / 289

PROLOG 7J [4.3] Przykład nieco dłuższy: 1?- b(x,a)=b(f(y),y), 2 d(f(f(a )))= d(u), 3 c(x)=c(f(z )). 4 5 U = f(f(a)) 6 X = f(a) 7 Y = a 8 Z = a 9 10 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 259 / 289

PROLOG 7J [4.3] Unifikacje i porównywanie termów ( manual 8.2, 8.3) = true, gdy unifikacja możliwa \= true, gdy unifikacja niemożliwa == true, gdy porównanie udane \== true, gdy porównanie nieudane @< true, gdy term mniejszy @=< true, gdy term niewiększy @> true, gdy term większy @>= true, gdy term niemniejszy porównanie ma sens dla termów tego samego typu używany schemat leksykograficzny wynikiem jest true lub false Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 260 / 289

PROLOG 7J [4.3] Przykłady porównań: 1?- X=" Reksio ", X==" Reksio ". 2 X = [82,101,107,115,105,111] 3 yes 4 5?- X==" Reksio ". 6 no 1?- " Reksio " @> " reksio ". 2 no 3 4?- " reksio " @> " Reksio ". 5 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 261 / 289

PROLOG 7J [4.3] Porównania numeryczne i leksykograficzne: 1?- X=55, Y=6, X<Y. 2 no 3 4?- X ="55", Y ="6", X<Y. 5 uncaught exception : 6 error ( type_error ( evaluable,. /2),( <)/2) 7 8?- X ="55", Y ="6", X@ <Y. 9 X = [53,53] 10 Y = [54] 11 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 262 / 289

Typy termów ( manual 6.3.4, 8.1) PROLOG 7J [4.3] var(t) nonvar(t) atom(t) atomic(t) number(t) compound(t) true, gdy T jest zmienną true, gdy T nie jest zmienną true, gdy T jest atomem (stałą lub napisem) true, gdy T jest atomem lub liczbą true, gdy T jest liczbą true, gdy T jest złożony Atom w prologu to stała lub łańcuch znakowy. 1 var (A). /* yes */ 2 var (a). /* no */ 3 var (2). /* no */ 4 var ( tekst ). /* no */ 5 atom (A). /* no */ 6 atom (a). /* yes */ 7 atom (2). /* no */ 8 atom ( tekst ). /* yes */ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 263 / 289

PROLOG 7J [4.3] Arytmetyka ( manual 8.6) =:= równe =\= nierówne < mniejsze =< niewiększe > większe >= niemniejsze + plus - minus * razy / podzielić // cz. całk. z dzielenia mod reszta z dzielenia Infiksowy funktor is podstawia pod swój lewy argument wyrażenie arytmetyczne (lub jego wynik) stojące po jego prawej stronie. 1 srednia (A,B,S) :- S is (A+B )/2. 2 1 is mod (7,2) /* true */ 3 3 is 7//2 /* true */ 4 3.5 is 7/2 /* true */ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 264 / 289

PROLOG 7J [4.3] Inne opearcje matematyczne wbudowane w GNU Prologu potęgowanie, pierwiastek kwadratowy bitowe opearcje and, or, xor, not wartość bezwzględna, znak funkcje trygonometryczne, odwrotne trygonometryczne i hiperboliczne logarytmy Przykład: 3 duże pizze (32cm) vs. 2 pizze XXL (40cm). Czego jest więcej? 1 area (R,A) :- A is (pi * R ^2). 2 3?- area (16,A), C is 3*A, area (20,B), D is 2*B. 4 5 A = 80 4.24 77 19 31 89 87 04 6 B = 12 56.6 37 06 14 35 91 73 7 C = 24 12.7 43 15 79 56 96 11 8 D = 25 13.2 74 12 28 71 83 46 9 10 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 265 / 289

PROLOG 7J [4.3] Liczba π otrzymana z odwrotnej funkcji trygonometrycznej: 1?- X is 4.0* atan (1.0). 2 3 X = 3.14 15 92 65 35 89 79 31 4 5 yes Wynik jest dokładny, więc użycie wbudowanej liczby pi daje to samo: 1?- X is 4.0* atan (1.0) - pi. 2 3 X = 0.0 4 5 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 266 / 289

PROLOG 7J [4.3] Definicje rekurencyjne Analiza, jak prolog szuka rozwiązań (przykład wzięty z notatek autorstwa P. Fulmańskiego, Uniwersytet Łódzki, 2009). Rozważmy kod: 1 mniej (p1,p2 ). 2 mniej (p2,p3 ). 3 mniej (p3,p4 ). 4 mniej (X,Y) :- mniej (X,Z), mniej (Z,Y). który powinien, na pierwszy rzut oka, wygenerować wszystkie pary z ciągu p1 < p2 < p3 < p4. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 267 / 289

PROLOG 7J [4.3] Tymczasem... 1?- mniej (A,B). A = p1 2 B = p3? ; 3 A = p1 4 B = p2? ; A = p1 5 B = p4? ; 6 A = p2 7 B = p3? ; Fatal Error : local stack 8 overflow ( size : 16384 Kb, 9 A = p3 reached : 16384 Kb, 10 B = p4? ; environment variable 11 used : LOCALSZ ) program wpada w nieskończoną pętle, zużywa dostępną mu pamięć i kończy pracę generując kod błędu. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 268 / 289

PROLOG 7J [4.3] 3 pierwsze odpowiedzi (p1,p2), (p2,p3), (p3,p4), pochodzą bezpośrednio z bazy wiedzy. Kolejne generowane są regułą mniej(x,y) :- mniej(x,z), mniej(z,y). jeśli X=p1 wtedy mniej(x,z) wskazuje na Z=p2 i w efekcie Y=p3, co tworzy rozwiązanie (p1,p3) mając X=p1, Z=p2, system szuka dalszych rozwiązań dla wyrażenia mniej(p2,y). Posługuje się regułą tworząc mniej(p2,y) :- mniej(p2,zz), mniej(zz,y), która prowadzi do ZZ=p3, Y=p4 i rozwiązania (p1,p4) poszukiwane jest kolejne rozwiązanie dla mniej(zz,y) = mniej(p3,y); korzystając z reguły mniej(p3,y) :- mniej(p3,z3), mniej(z3,y) znaleźć można tylko Z3=p4, które spełnia mniej(p3,z3); żadna reguła nie spełnia jednak mniej(p4,y), więc rozwijana jest ona mniej(p4,y) :- mniej(p4,z4), mniej(z4,y); czego nie spełnia żadna reguła, ale można ją rozwinąć: mniej(p4,z4) :- mniej(p4,z5), mniej(z5,z4) i tak dalej... Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 269 / 289

PROLOG 7J [4.3] Problem leży w wywołaniu reguły mniej(x,y), która korzysta sama z siebie bez żadnej kontroli. Prowadzi to do niekończącej się pętli. Definicje rekurencyjne wymagają starannego zaprojektowania w szczególności ważny jest warunek zakończenia rekurencji. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 270 / 289

PROLOG 7J [4.3] Przykład programu w prologu Konwersja między stopniami Celsiusza i Fahrenheita. 1 run :- write ( Podaj temp. w st. Celsiusza ), 2 read (C), convert (C,F), 3 write ( Temp. to ), write (F), 4 write ( st. Fahrenheita ), nl, 5 warning (F, Komunikat ), write ( Komunikat ). 6 7 convert (C,F) :- F is 9./5.* C + 32. 8 9 warning (T, Ale upal! ) :- T > 90. 10 warning (T, Ale ziab! ) :- T < 30. 11 warning (T, ) :- T >= 30, T =< 90. Źródło: mcsp.wartburg.edu/zelle/cs373/handouts/prologexamples/prologintro.pdf Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 271 / 289

PROLOG 7J [4.3] Listy Prolog rozpoznaje dwa sposoby deklarowania list: zapis [a,b,c] jest przyjazny dla użytkownika funktor prefiksowy. (kropka) dodaje term na początek listy Zapis w nawiasach kwadratowych jest wewnętrznie konwertowany do równoważnego zapisu kropkowego. Lista pusta to []. 1?- L = [a,b,c], M =.(a,.(b,.(c,[]))), L=M. 2 3 L = [a,b,c] 4 M = [a,b,c] 5 6 yes /* L można zunifikować z M */ Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 272 / 289

PROLOG 7J [4.3] Lista jest automatycznie dzielona na głowę (pierwszy element) i ogon (całą resztę). Zapisywane jest to za pomocą operatora (pionowa kreska) [H T]. 1?- L = [a,b,c], L = [H T]. 2 3 H = a 4 L = [a,b,c] 5 T = [b,c] 6 7 yes Poniższe zapisy dotyczą tej samej listy 1 [a,b,c] = [a [b,c]] = [a,b [c]] = [a,b,c []] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 273 / 289

PROLOG 7J [4.3] Przykłady: 1?- []=[ H T]. 2 No lista pusta nie ma głowy ani ogona 1?- [1]=[ H T]. 2 H = 1 3 T = [] dla listy jednoelementowej ogon jest listą pustą 1?- [1,2]=[ H T]. 2 H = 1 3 T = [2] głowa to pojedynczy term, ogon jest zawsze listą Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 274 / 289

Inne przykłady: 1?- [1,[2,3]]=[ H T]. 2 H = 1 PROLOG 7J [4.3] 3 T = [[2,3]] /* ogon to lista list */ 4 5?- [[1,2],3]=[ H T]. 6 H = [1,2] /* głowa jest listą */ 7 T = [3] 8 9?- [1,2,3,4]=[ Ha,Hb T]. 10 Ha = 1 11 Hb = 2 12 T = [3,4] 13 14?- [[1,2,3],4]=[[ H1 T1 ] T2 ]. 15 H1 = 1 16 T1 = [2,3] 17 T2 = [4] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 275 / 289

PROLOG 7J [4.3] Listy nie są ograniczone do elementów jednego typu 1?- L = [[X,a], bolek,[],[ Celina,[]]], 2 L = [H T]. 3 4 H = [X,a] 5 L = [[X,a], bolek,[],[ Celina,[]]] 6 T = [ bolek,[],[ Celina,[]]] 1?- L = [A,B C], L = [ala, bolek, [1,2,3]]. 2 3 A = ala 4 B = bolek 5 C = [[1,2,3]] 6 L = [ala, bolek,[1,2,3]] zauważcie, że C nie jest równe [1,2,3]! Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 276 / 289

PROLOG 7J [4.3] Przykład: append(l1,l2,m) sprawdza, czy M=L1+L2 1?- L1 =[a,b,c], L2 =[d,e,f], append (L1,L2,M) 2 3 L1 = [a,b,c] 4 L2 = [d,e,f] 5 M = [a,b,c,d,e,f] 6 7 yes Schemat działania scalania list append(l1,l2,m): rozłóż jedną z list (L1 lub L2) na osobne elementy dodawaj po jednym elemencie rozłożonej listy na początek lub koniec drugiej listy zunifikuj wynik z ostatnim argumentem wywołania (M) Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 277 / 289

PROLOG 7J [4.3] Problem: Zdefiniuj concat/3, aby działało jak append/3. Strategia 1 przy wywołaniu concat1(l1,l2,m) usuń pierwszy element z L2 dopisz go na końcu L1 powtarzaj aż L2=[] zunifikuj wynik z M 1 concat1 (L,[],L). 2 concat1 (L1,L2,M) :- L2 =[E R], Z=[ L1 E], 3 concat1 (Z,R,M). 4 5?- L1 =[a,b,c], L2 =[d,e,f], concat1 (L1,L2,M). 6 L1 = [a,b,c] 7 L2 = [d,e,f] 8 M = [[[[a,b,c] d] e] f]? ; 9 no Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 278 / 289

PROLOG 7J [4.3] Strategia 2 przy wywołaniu concat2(l1,l2,m) usuń ostatni element z L1 dopisz go do początku L2 powtarzaj aż L1=[] zunifikuj wynik z M 1 concat2 ([],L,L). 2 concat2 (L1,L2,M) :- L1 =[P K], concat2 (K,L2,Z), 3 M=[P Z]. 4 5?- L1 =[a,b,c], L2 =[d,e,f], concat2 (L1,L2,M). 6 L1 = [a,b,c] 7 L2 = [d,e,f] 8 M = [a,b,c,d,e,f]? ; 9 no Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 279 / 289

PROLOG 7J [4.3] Strategia 1 rekurencja niby prostsza wynik wymaga spłaszczenia do pojedynczej listy Strategia 2 rekurencja mniej przejrzysta, bo wymaga rozłożenia całej listy i pracy od końca wynik jest poprawny jest to standardowa konstrukcja w prologu (podejście bottom-up) Praca domowa Zastanówcie się, dlaczego strategia top-down w tym przypadku zawodzi? Czy da się to poprawić (może jest błąd w mojej definicji)? W czym tkwi róźnica pomiędzy dwoma, zdawałoby się logicznie równoważnymi, podejściami? A może prolog jest niesymetryczny i te dwa podejścia nie są dla niego równoważne z powodu którejś z użytych konstrukcji? Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 280 / 289

PROLOG 7J [4.3] Przykład: Pięciu przyjaciół brało udział w wyścigu. Willy nie wygrał. Gregory przybiegł trzeci, za Danielem. Daniel nie zajął drugiego miejsca. Andrew nie wygrał ani nie był ostatni. Bill przybiegł zaraz za Willym. Ustal kolejność zawodników. 1 place (W,G,D,A,B) :- L0 =[1,2,3,4,5], G=3, 2 select (W,L0,L1), W\=1, 3 select (D,L1,L2), D\=2, 4 select (A,L2,L3), A\=1, A\=5, 5 select (B,L3,[G]), B is W+1, 6 G>D. 7?- place ( Willy, Gregory, Daniel, Andrew, Bill ). 8 Andrew = 2 9 Bill = 5 10 Daniel = 1 11 Gregory = 3 12 Willy = 4? 13 yes Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 281 / 289

PROLOG 7J [4.3] Przykład: Wieże Hanoi XXX XXXXX XXXXXXX XXXXXXXXX =============================== Zasady gry Przełożyć krążki na inny pręt: tylko jeden krążek może być przekładany na raz tylko szczytowy krążek może być przekładany nie można położyć krążka na mniejszym krążku Najmniejsza liczba ruchów potrzebna do rozwiązania układanki to 2 n 1, gdzie n jest liczbą krążków. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 282 / 289

PROLOG 7J [4.3] Strategia: [Clocksin, Mellish, rozdz. 7.4] Trzy pręty to: start (source), meta (destination) i jeden pomocniczy (temp). Rozwiązanie jest rekurencyjne. Dla n krążków: 1 przesuń n 1 krążków ze startu na pomocniczy 2 przesuń n-ty (największy) dysk ze startu na metę 3 przesuń n 1 dysków z pomocniczego na metę aby wykonać (1) trzeba przesunąć n 2 krążków ze startu na (inny) pomocniczy itd., więc procedura jest rekurencyjna n musi być podane na wejściu do programu Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 283 / 289

PROLOG 7J [4.3] Program rozwiązujący wieże Hanoi: 1 hanoi (N) :- move (N,source, destination, temp ). 2 3 move (0,_,_,_) :-!. 4 move (N,A,B,C) :- M is N -1, 5 move (M,A,C,B), 6 info (A,B), 7 move (M,C,B,A). 8 9 info (X,Y) :- write ([X,-->,Y]), nl. gdzie lewy pręt to source, środkowy destination, prawy temporary. Uwaga: w programie nie ma algorytmu rozwiązującego układankę, opisana jest reguła poprawnego przełożenia krążka między prętami, Prolog sam znajduje rozwiązanie na podstawie punktu startowego i podanych reguł. Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 284 / 289

PROLOG 7J [4.3] 1?- hanoi (3). 2 [left,-->, center ] 3 [left,-->, right ] 4 [ center,-->, right ] 5 [left,-->, center ] 6 [ right,-->, left ] 7 [ right,-->, center ] 8 [left,-->, center ] Dla 3 dysków potrzeba 2 3 1 = 7 ruchów, dla 4 dysków wymagane jest 2 4 1 = 15 ruchów. 1?- hanoi (4). 2 [left,-->, right ] 3 [left,-->, center ] 4 [ right,-->, center ] 5 [left,-->, right ] 6 [ center,-->, left ] 7 [ center,-->, right ] 8 [left,-->, right ] 9 [left,-->, center ] 10 [ right,-->, center ] 11 [ right,-->, left ] 12 [ center,-->, left ] 13 [ right,-->, center ] 14 [left,-->, right ] 15 [left,-->, center ] 16 [ right,-->, center ] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 285 / 289

Przykład: drzewo genealogiczne PROLOG 7J [4.3] Marek Góźdź (2016/2017) JiPP 6 czerwca 2017 286 / 289