Składnia funkcji i w języku Haskell Tomasz Ostrowski, Adrian Niechciał, Michał Workiewicz, Marcin Wilk 26 marca 2015 Składnia funkcji i w języku Haskell
Spis treści Składnia funkcji Tomasz Ostrowski Adrian Niechciał Michał Workiewicz Marcin Wilk Składnia funkcji i w języku Haskell
Tomasz Ostrowski Dopasowywanie wzorców (pattern matching) W języku Haskell możliwe jest zdefiniowanie kilku różnych ciał funkcji, których wywołanie będzie zależne od konkretnych parametrów odpowiadających zadanym wzorcom. 1 f u n k c j a : : ( I n t e g r a l a ) => a > S t r i n g 2 f u n k c j a 1 = Wywołano parametr p i e r w s z y. 3 f u n k c j a 2 = Wywołano parametr d r u g i. 4 f u n k c j a x = Nie rozpoznano parametru. W takim wypadku wszystkie możliwości są sprawdzane od góry do dołu, i kiedy podany na wejściu parametr odpowiada jednemu z wzorców, wtedy zostaje wywołane odpowiadające mu ciało funkcji Składnia funkcji i w języku Haskell
Tomasz Ostrowski Kiedy możliwe jest wprowadzenie wartości nie występującej w żadnym z wzorców, korzysta się z wzorca który przyjmuje każdy parametr. 1 t a r c z a : : ( I n t e g r a l a ) => a > S t r i n g 2 t a r c z a 1 = T r a f i l e s 1 punkt. 3 t a r c z a 2 = T r a f i l e s 2 punkty. 4 t a r c z a 3 = T r a f i l e s 3 punkty. 5 t a r c z a 4 = T r a f i l e s 4 punkty. 6 t a r c z a 5 = T r a f i l e s 5 punktow. 7 t a r c z a 6 = T r a f i l e s 6 punktow. 8 t a r c z a 7 = T r a f i l e s 7 punktow. 9 t a r c z a 8 = T r a f i l e s 8 punktow. 10 t a r c z a 9 = T r a f i l e s 9 punktow. 11 t a r c z a 10 = G r a t u l a c j e! T r a f i l e s w 1 0! 12 t a r c z a x = Nie t r a f i l e s w t a r c z e! UWAGA: Ponieważ Haskell sprawdza możliwości zaczynając od góry, w przypadku użycia takiego wzorca musimy umieścić go na końcu kodu. W innym wypadku wzorce znajdujące się po nim nie zostaną w ogóle sprawdzone. Składnia funkcji i w języku Haskell
Tomasz Ostrowski Jeżeli nie przewidzimy możliwości występowania dowolnego parametru, na przykład: 1 charname : : Char > S t r i n g 2 charname a = A l b e r t 3 charname b = Broseph 4 charname c = C e c i l I wywołamy nieznany parametr: 1 g h c i > charname h Haskell wyświetli następujący błąd: 1 E x c e p t i o n : t u t. hs : ( 5 3, 0) (55, 21) : Non e x h a u s t i v e p a t t e r n s i n f u n c t i o n charname Najbezpieczniejsze jest więc stosowanie zawsze na koniec takiej funkcji wzorca akceptującego dowolny parametr. Składnia funkcji i w języku Haskell
Tomasz Ostrowski Krotki Wzorce można także stosować na krotkach. Poniższy przykład ilustruje funkcję, która dodaje do siebie wektory. 1 dodajwektory : : ( Num a ) = > ( a, a ) > ( a, a ) > ( a, a ) 2 dodajwektory ( x1, y1 ) ( x2, y2 ) = ( x1 + x2, y1 + y2 ) Tak skonstruowana funkcja przyjmuje na wejście dowolne zmienne i zwraca wynik w postaci dwóch par liczb (wektorów). Jest również możliwe stosowanie wzorców na listach. W tym przykładzie wyszukiwane i sumowane są elementy listy odpowiadające zadanemu wzorcowi (a, b). 1 g h c i > l e t x s = [ ( 1, 3 ), ( 4, 3 ), ( 2, 4 ), ( 5, 3 ), ( 5, 6 ), ( 3, 1 ) ] 2 g h c i > [ a+b ( a, b ) < x s ] 3 [ 4, 7, 6, 8, 1 1, 4 ] W przypadku kiedy dopasowanie wzorca jest niemożliwe, funkcja przechodzi do następnego elementu listy. Składnia funkcji i w języku Haskell
Tomasz Ostrowski Dopuszczalne jest także użycie listy pustej w określaniu wzorca. Tu na przykładzie zmodyfikowanej funkcji head: 1 h e a d : : [ a ] > a 2 h e a d [ ] = e r r o r Nie da s i ę wyznaczyc glowy p u s t e j l i s t y! 3 h e a d ( x : ) = x I z zastosowaniem rekurencji, w zmodyfikowanej wersji funkcji sum, która dodaje do siebie wszystkie elementy listy: 1 s u m : : ( Num a ) => [ a ] > a 2 s u m [ ] = 0 3 s u m ( x : x s ) = x + s u m x s Można również za pomocą wzorca odwołać się do całości dowolnej listy, bez potrzeby przepisywania w każdym miejscu jej nazwy lub definicji. W tym celu stosuje się znak @ wstawiany bezpośrednio przed wzorcem. 1 p i e r w s z a L i t e r a : : S t r i n g > S t r i n g 2 p i e r w s z a L i t e r a wyraz@ ( x : x s ) = P i e r w s z a l i t e r a s l o w a ++ wyraz ++ j e s t ++ [ x ] Składnia funkcji i w języku Haskell
Tomasz Ostrowski Osłony (guards) Osłony spełniają taką samą funkcję jak wyrażenie if służą do stwierdzenia czy postawiony warunek został spełniony. W odróżnieniu jednak od składni if, osłony są bardziej czytelne przy dużej liczbie warunków dla jednego wyrażenia oraz dobrze współgrają z haskellowymi wzorcami. 1 t a r c z a : : ( I n t e g e r a ) => a > S t r i n g 2 t a r c z a c e l 3 c e l <= 3 = S l a b i u t k o, m u s i s z s i ę b a r d z i e j p o s t a r a c. 4 c e l <= 5 = Troche l e p i e j, a l e j e s z c z e d a l e k o do i d e a l u. 5 c e l <= 8 = Prawie c i s i ę u d a l o. 6 c e l <= 10 = Znakomicie! T r a f i l e s w d z i e s i a t k e! 7 o t h e r w i s e = Porazka! Nie t r a f i l e s w t a r c z e! Składnia funkcji i w języku Haskell
Tomasz Ostrowski Osłony oznacza się znakiem, każda osłona jest wyrażeniem typu boolean przyjmującym wartość True lub False. Znajdujące się na końcu wyrażenie otherwise jest odpowiednikiem else znanego z konstrukcji if-else. Inny przykład zastosowania: 1 max : : ( Ord a ) => a > a > a 2 max a b 3 a > b = a 4 o t h e r w i s e = b Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula where Miejsce, w którym wyrażeniom lub wartościom przypisuje się nazwę, tworząc stałe. Umieszcza się ją po osłonach. Jest przydatna, gdy funkcja zawiera obliczenia, które muszą być kilkakrotnie powtarzane. //przykład 1 Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula where c.d. Możemy pójść o krok dalej i przekształcić naszą funkcję w następujący sposób: //przykład 2 Wszystkie wartości zdefiniowane w where są widoczne tylko w obrębie danej funkcji. Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula where c.d. Sekcja where pozwala również na dopasowywanie wzorców. //przykład 3 Lepiej jednak robić to, definiując ciała funkcji, ale ten przykład pokazuje, że takie coś jest możliwe. Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula where c.d. Tak jak wcześniej definiowaliśmy za pomocą where stałe, istnieje również możliwość definiowania funkcji. Stwórzmy funkcję, która przyjmuje listę par (waga, wzrost) i dla każdej takiej pary zwraca BMI. //przykład 4 Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let Jest podobna do klauzuli where, co widać na przykładzie: //przykład 5 Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Jest jednak między nimi istotna różnica. Otóż where jest tylko częścią składni i nie może istnieć samodzielnie, natomiast klauzula let jest wyrażeniem, więc możemy używać jej gdziekolwiek zechcemy. Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Przykłady użycia let Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Przykłady użycia let 1. Wstawienie funkcji do listy: 1 [ l e t kwadrat x = x x i n ( kwadrat 5, kwadrat 3, kwadrat 2) ] Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Przykłady użycia let 1. Wstawienie funkcji do listy: 1 [ l e t kwadrat x = x x i n ( kwadrat 5, kwadrat 3, kwadrat 2) ] 2. Rozbicie krotki na komponenty i przypisanie im nazw: 1 ( l e t ( a, b, c ) = ( 1, 2, 3) i n a + b + c ) 100 Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Przykłady użycia let 1. Wstawienie funkcji do listy: 1 [ l e t kwadrat x = x x i n ( kwadrat 5, kwadrat 3, kwadrat 2) ] 2. Rozbicie krotki na komponenty i przypisanie im nazw: 1 ( l e t ( a, b, c ) = ( 1, 2, 3) i n a + b + c ) 100 3. Uniknięcie tworzenia funkcji pomocniczych: //przykład 6 Składnia funkcji i w języku Haskell
Adrian Niechciał Klauzula let c.d. Przykłady użycia let 1. Wstawienie funkcji do listy: 1 [ l e t kwadrat x = x x i n ( kwadrat 5, kwadrat 3, kwadrat 2) ] 2. Rozbicie krotki na komponenty i przypisanie im nazw: 1 ( l e t ( a, b, c ) = ( 1, 2, 3) i n a + b + c ) 100 3. Uniknięcie tworzenia funkcji pomocniczych: //przykład 6 4. Tworzenie w konsoli zmiennych i funkcji, do których mamy dostęp do momentu zamknięcia konsoli: 1 l e t z o o t x y z = x y + z Składnia funkcji i w języku Haskell
Adrian Niechciał Wyrażenie warunkowe case Jest oparte na dopasowywaniu do wzorca i rozszerza je, bo dopasowywanie do wzorca mogło być użyte tylko w definicji funkcji, a wyrażeń warunkowych możemy użyć tam, gdzie to potrzebne. //przykład 7 Składnia funkcji i w języku Haskell
Michał Workiewicz zwana również rekursją, polega na wywołaniu przez funkcję samej siebie. Algorytmy rekurencyjne zastępują w pewnym sensie iteracje. Zazwyczaj zadania rozwiązywane tą techniką są wolniejsze od iteracyjnego odpowiednika, natomiast rozwiązanie niektórych problemów jest znacznie wygodniejsze. Składnia funkcji i w języku Haskell
Michał Workiewicz c.d. Rekursja odgrywa podczas programowania w Haskellu bardzo dużą rolę. Nie istnieją w tym języku instrukcje takie jak pętla for czy do-while. Implementacja często powtarzających się czynności musi zatem zostać rozwiązana przez rekurencję. Dobrymi przykładami rekurencji są ciąg Fibonacciego oraz silnia. Składnia funkcji i w języku Haskell
Michał Workiewicz Ciąg Fibonacciego 1 f i b 0 = 0 2 f i b 1 = 1 3 f i b n = f i b ( n 1) + f i b ( n 2) Składnia funkcji i w języku Haskell
Michał Workiewicz Silnia 1 f a c t o r i a l n = i f n < 2 then 1 e l s e n f a c t o r i a l ( n 1) lub 1 f a c t o r i a l 0 = 1 2 f a c t o r i a l n = n f a c t o r i a l ( n 1) Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum Funkcja maximum zwraca największy element z zadanego zbioru, dlatego też musi być zabezpieczona na wypadek gdyby zbiór był pusty, lub zawierał tylko jeden element. By ułatwić funkcji działanie, dzielimy ją na 3 wzorce. Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximumof empty l i s t 1 3 maximum [ x ] = x 2 4 maximum ( x : x s ) 3 5 x > maxtail = x 6 o t h e r w i s e = maxtail 7 wheremaxtail = maximum x s Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximumof empty l i s t 1 3 maximum [ x ] = x 2 4 maximum ( x : x s ) 3 5 x > maxtail = x 6 o t h e r w i s e = maxtail 7 wheremaxtail = maximum x s 1. Zbiór jest pusty zwracamy błąd o podanej treści. Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximumof empty l i s t 1 3 maximum [ x ] = x 2 4 maximum ( x : x s ) 3 5 x > maxtail = x 6 o t h e r w i s e = maxtail 7 wheremaxtail = maximum x s 1. Zbiór jest pusty zwracamy błąd o podanej treści. 2. Zbiór jest jednoelementowy funkcja zwraca jedyny element tablicy. Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximumof empty l i s t 1 3 maximum [ x ] = x 2 4 maximum ( x : x s ) 3 5 x > maxtail = x 6 o t h e r w i s e = maxtail 7 wheremaxtail = maximum x s 1. Zbiór jest pusty zwracamy błąd o podanej treści. 2. Zbiór jest jednoelementowy funkcja zwraca jedyny element tablicy. 3. Zbiór wieloelementowy podana funkcji tablica ma więcej niż jeden element. W tym miejscu działa rekurencja. Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximumof empty l i s t 1 3 maximum [ x ] = x 2 4 maximum ( x : x s ) 3 5 x > maxtail = x 6 o t h e r w i s e = maxtail 7 wheremaxtail = maximum x s 1. Zbiór jest pusty zwracamy błąd o podanej treści. 2. Zbiór jest jednoelementowy funkcja zwraca jedyny element tablicy. 3. Zbiór wieloelementowy podana funkcji tablica ma więcej niż jeden element. W tym miejscu działa rekurencja. Funkcja dzieli listę na head i tail, aż do momentu otrzymania listy jednoelementowej i porównuje head, z maximum tail (czyli wywołuje funkcje maximum dla tail). Składnia funkcji i w języku Haskell
Michał Workiewicz Maximum c.d. Możemy też uprościć sobie tę funkcję poprzez zastosowanie funkcji max, pobierającej dwa argumenty i zwracającej większy z nich. 1 maximum : : ( Ord a ) => [ a ] > a 2 maximum [ ] = e r r o r maximum o f empty l i s t 3 maximum [ x ] = x 4 maximum ( x : x s ) = max x (maximum x s ) Składnia funkcji i w języku Haskell
Michał Workiewicz Przeanalizujmy teraz działanie rekurencji na podstawie funkcji maximum. Weźmy tablicę liczb całkowitych [2,1,5]. Składnia funkcji i w języku Haskell
Michał Workiewicz Przeanalizujmy teraz działanie rekurencji na podstawie funkcji maximum. Weźmy tablicę liczb całkowitych [2,1,5]. Tablicę dzielimy na head(2) i tail([1,5]) i wywołujemy funkcję maximum na tail. Składnia funkcji i w języku Haskell
Michał Workiewicz Przeanalizujmy teraz działanie rekurencji na podstawie funkcji maximum. Weźmy tablicę liczb całkowitych [2,1,5]. Tablicę dzielimy na head(2) i tail([1,5]) i wywołujemy funkcję maximum na tail. Tail jest dzielony znów na head i tail. Otrzymujemy 1 i 5 czyli dwa zbiory jednoelementowe został spełniony warunek końca rekurencji. Składnia funkcji i w języku Haskell
Michał Workiewicz Przeanalizujmy teraz działanie rekurencji na podstawie funkcji maximum. Weźmy tablicę liczb całkowitych [2,1,5]. Tablicę dzielimy na head(2) i tail([1,5]) i wywołujemy funkcję maximum na tail. Tail jest dzielony znów na head i tail. Otrzymujemy 1 i 5 czyli dwa zbiory jednoelementowe został spełniony warunek końca rekurencji. Porównujemy 1 i 5, funkcja zwraca 5 jako większe. Składnia funkcji i w języku Haskell
Michał Workiewicz Przeanalizujmy teraz działanie rekurencji na podstawie funkcji maximum. Weźmy tablicę liczb całkowitych [2,1,5]. Tablicę dzielimy na head(2) i tail([1,5]) i wywołujemy funkcję maximum na tail. Tail jest dzielony znów na head i tail. Otrzymujemy 1 i 5 czyli dwa zbiory jednoelementowe został spełniony warunek końca rekurencji. Porównujemy 1 i 5, funkcja zwraca 5 jako większe. Porównujemy zwróconą wartość z head naszej tablicy. 5 jest oczywiście większe niż 2, zatem funkcja zwraca 5 jako największą wartość. Składnia funkcji i w języku Haskell
Michał Workiewicz Łączenie łańcuchów na przemian 1 polacznaprzemian : : [ a] >[a] >[a ] 2 polacznaprzemian [ ] = [ ] 3 polacznaprzemian [ ] = [ ] 4 polacznaprzemian ( x : x s ) ( y : y s ) = [ x, y ] ++ polacznaprzemian x s y s Składnia funkcji i w języku Haskell
Michał Workiewicz Przykłady c.d. Mnożenie przez dodawanie: 1 m u l t i p l y a 0 = 0 2 m u l t i p l y a b = a + m u l t i p l y a ( b 1) Potęgowanie: 1 i n v o l u t i o n a 0 = 0 2 i n v o l u t i o n a b = a i n v o l u t i o n a ( b 1) Składnia funkcji i w języku Haskell
Marcin Wilk QuickSort w Haskellu Załóżmy, że mamy listę elementów typu Ord do posortowania. Najefektywniej można to osiągnąć za pomocą popularnego algorytmu QuickSort. O ile jednak w językach imperatywnych taki algorytm zajmować może nawet kilkanaście linijek kodu, to jego implementacja w Haskellu jest dużo krótsza i bardziej przejrzysta. Składnia funkcji i w języku Haskell
Marcin Wilk QuickSort w Haskellu c.d. Nagłówek funkcji wygląda tak: 1 q u i c k s o r t : : ( Ord a ) => [ a ] > [ a ] Warunek końca określa sytuację, kiedy do posortowania zostaje pusta lista (co jest równoznaczne z tym, że jest już posortowana): 1 q u i c k s o r t [ ] = [ ] Składnia funkcji i w języku Haskell
Marcin Wilk QuickSort w Haskellu c.d. Później przychodzi pora na główny algorytm, który działa w ten sposób: Elementy listy, które są mniejsze lub równe headowi są sortowane i umieszczane na początku listy, następnie wstawiany jest head, a po nim posortowane elementy większe lub równe headowi. Składnia funkcji i w języku Haskell
Marcin Wilk QuickSort w Haskellu c.d. Warto zaznaczyć, że w tym algorytmie sortowanie wykonywane jest dwukrotnie (dla elementów mniejszych i większych od head). Tak więc do użycia QuickSorta potrzebne będzie skorzystanie z rekurencji, która jest jednym z najważniejszych elementów programowania funkcyjnego. Do sprawdzenia, które elementy listy są większe, a które mniejsze od head zastosujemy porównanie dwóch list. Składnia funkcji i w języku Haskell
Marcin Wilk QuickSort w Haskellu c.d. Całość funkcji QuickSort w języku Haskell wygląda tak: 1 q u i c k s o r t : : ( Ord a ) => [ a ] > [ a ] 2 q u i c k s o r t [ ] = [ ] 3 q u i c k s o r t ( x : x s ) = 4 l e t s m a l l e r S o r t e d = q u i c k s o r t [ a a < x s, a <= x ] 5 b i g g e r S o r t e d = q u i c k s o r t [ a a < x s, a > x ] 6 i n s m a l l e r S o r t e d ++ [ x ] ++ b i g g e r S o r t e d Przykłady działania: 1 g h c i > q u i c k s o r t [ 1 0,2,5,3,1,6,7,4,2,3,4,8, 9 ] 2 [ 1,2,2,3,3,4,4,5,6,7,8,9, 1 0 ] 1 g h c i > q u i c k s o r t t h e q u i c k brown f o x jumps o v e r t h e l a z y dog 2 a b c d e e e f g h h i j k l m n o o o o p q r r s t t u u v w x y z Składnia funkcji i w języku Haskell
Marcin Wilk Myślenie rekurencyjne Chociaż wiele problemów można rozwiązać za pomocą stosowania rekurencji, istnieją także przypadki skrajne, w których użycie rekursji nie ma sensu. Na przykład: w przypadku list, takim skrajnym przypadkiem jest pusta lista, natomiast w przypadku drzewa będzie. To drzewo nie posiadające żadnych dzieci. Składnia funkcji i w języku Haskell
Marcin Wilk Myślenie rekurencyjne c.d. Na podobne przypadki można się natknąć podczas rekurencyjnego operowania na liczbach. Zazwyczaj dochodzi do tego przy stosowaniu funkcji działających na liczbach poddawanych modyfikacjom. Przykładem takiej funkcji jest silnia, która nie zadziała na rekurencyjnie na liczbach począwszy od zera, ponieważ silnię można obliczyć jedynie dla całkowitych liczb dodatnich. Składnia funkcji i w języku Haskell
Marcin Wilk Myślenie rekurencyjne c.d. Często również przypadkiem skrajnym może okazać się identyczność. Na przykład identycznością dla mnożenia jest liczba 1, ponieważ jakakolwiek liczba pomnożona przez 1 da tę samą liczbę. W przypadku sumowania list, sytuacją skrajną może być dodawanie pustej listy, której suma jest równa 0. Składnia funkcji i w języku Haskell
Marcin Wilk Myślenie rekurencyjne c.d. Podsumowując: Kiedy próbujemy rozwiązać rekurencyjnie jakiś problem, dobrze jest wiedzieć jakie są przypadki, w których nie da się dobrze zastosować rekurencji, żeby użyć ich jako przypadków skrajnych pomocnych w rozwiązaniu danego problemu. Składnia funkcji i w języku Haskell
Marcin Wilk Dziękujemy za uwagę. Składnia funkcji i w języku Haskell