Haskell - Typy i funkcje
Transkrypt
Haskell - Typy i funkcje
Haskell jest statycznym typem języka: - wszystkie typy i wyrażenia są znane w czasie kompilacji ( zwiększa to bezpieczeństwo kodu). Podawanie typów zmiennych i argumentów funkcji jest zbędne, gdyż Haskel wywnioskuje to z kontekstu. Nazwy wszystkich typów zaczynają się dużą literą. - nawiasy kwadratowe oznaczają, że to lista Aby stworzyć funkcję musimy ją zapisać w pliku nazwa_pliku.hs. Jeżeli rozszerzenie pliku będzie inne niż .hs wyświetli nam się taki komunikat: target ‘nazwa_pliku.a’ is not a module name or a source file Plik kompilujemy: ghci> :l nazwa_pliku.hs ghci> :l nazwa_pliku ghci> :load nazwa_pliku.hs ghci> :load nazwa_pliku W funkcjach powinniśmy deklarować jakiego typu są argumenty funkcji (nie wymagane) . Parametry oddzielamy przez ->. Parametry pobierane i zwracane nie są rozróżniane. Ostatni parametr na liście w deklaracji typów określa typ zwracany przez funkcję. Jeśli nie mamy pojęcia co nasza funkcja pobiera i zwraca możemy użyć :t. Typy całkowite Int jest to typ ograniczony całkowity. (liczba całkowita zapisywana na 32 bitach). Integer jest również typem całkowitym.(brak ograniczenia do 32 bitów). Integer pomieści znacznie większe liczby, aczkolwiek Int jest typem bardziej wydajnym. Float - pojedyncza precyzja Double - podwójna precyzja. Inne typy Bool, Char - znaki muszą być ujęte w apostrofy Zmienne typy Funkcja wbudowana head pobiera i zwraca: Nazwy typów zaczynają się z wielkiej litery, a więc a nie określa typu. Zapis ten oznacza, że funkcja head pobiera listę dowolnych elementów i zwraca jeden z nich (w tym przypadku akurat pierwszy). Zmienne typy w Haskell-u są tym czym są typy generyczne w innych językach. Funkcje zawierające zmienne typy są nazywane funkcjami polimorficznymi. Zmienne tych funkcji mogą posiadać zmienne o nazwach dłuższych niż jednoliterowe, ale przyjęło się, że używa się właśnie nazw o długości jednej litery. Zmienne typy funkcja fst: pobiera dwuelementową krotkę i zwraca element który jest tego samego typu co pierwszy element krotki. Elemeny a i b nie musza być tego samego typu. W Haskell operatory ==, +, *, -, / i wiele innych to funkcje. Jeżeli funkcja zawiera jedynie znaki specjalne, domyślnie jest funkcją infiksową. Jeżeli chcemy sprawdzić na jakich typach pracuje musimy przekazać ją do innej funkcji lub uczynić tę funkcję prefiksową czyli musimy nazwę tej funkcji zawrzeć w nawiasach. Wszystko co występuje przed symbolem => nazywane jest klasą ograniczeń. W przypadku funkcji == możemy to oczytać jako: funkcja równości pobiera dwa elementy tego samego typu należące do klasy obiektów (typeclass) Eq i zwraca typ Bool. Typeclass -rodzaj interfejsu definiującego pewne zachowania. Jeżeli typ jest częścią typeclass oznacza to, że ma zaimplementowane pewne zachowania, które opisuje typeclass. Nie jest tym samym co klasa w językach zorientowanych obiektowo. Przypomina raczej interfejsy w Java. Typeclass Eq -udostępnia interfejs do testowania równości. Każdy typ dla którego ma sens porównanie dwóch wartości powinien należeć do klasy Eq. Standard języka Haskell wymaga by wszystkie typy i funkcje, z wyjątkiem IO, należały do klasy Eq. Dlatego możemy wykonywać takie porównania: Typeclass Ord - jest dla typów które mają "kolejność". Funkcje porównujące >, <, >=, <= pobierają dwa argumenty tego samego typu należące do klasy Ord i zwracają obiekt klasy Ordering, tzn. GT, LT lub EQ (greater than, lesser than i equal) Aby typ należał do klasy Ord, musi należeć do klasy Eq. Typeclass Show - klasa obiektów które mogą być zaprezentowane jako tekst. Najczęściej używaną funkcją należącą do klasy typu Show jest funkcja show. Pobiera ona wartość i wyświetla ją jako tekst. Typeclass Read Funkcją o działaniu odwrotnym do show jest funkcja read. Pobiera ona tekst i zwraca typ należący do Read. Zdarzają się jednak czasami problemy: Wystąpił komunikat, gdyż funkcja nie wie co zwrócić. Nie zawsze kompilator może wywnioskować z kontekstu jakiego typu danych oczekuje. Należy zaznaczyć jakiego wyniku oczekujemy, by pozbyć się niechcianego błędu. Typeclass Enum - klasa obiektów do którego należą typy, które mogą być wyliczane. W typie tym dostęp do poprzednika udostępnia funkcja pred, a do następnika funkcja succ. Typy które należą do tej klasy to: (), Bool, Char, Ordering, Int, Integer, Float i Double. Typeclass Bounded - do tej klasy należą typy mające wartości graniczne. Funkcja minBound zwraca minimalną wartość danego typu, a funkcja maxBound zwraca maksymalną wartość danego typu. Wszystkie krotki również należą do tej klasy. Typeclass Num -klasa której członkowie mogą zachowywać się jak liczby. Jeżeli sprawdzimy Okazuje się, że funkcja ta akceptuje wszystkie typy należące do klasy Num. Pobiera ona dwie liczby tego samego typu i zwraca liczbę tego samego typu. (5 :: Int) * (6 :: Integer) - wygeneruje błąd, ale 5 * (6 :: Integer) wykona się ponieważ 5 może działać jak Integer Typeclass Integral - klasa obiektów należąca do klasy Num. Do Integral należą Int i Integer. Typeclass Floating - typy zmiennoprzecinkowe tzn. Float i Double. Funkcja fromIntegral konwertuje typy numeryczne na typ Integral. Bardzo użyteczna funkcja jeżeli chcemy używać razem typów zmiennoprzecinkowych z typami całkowitymi. Pierwsza linia to deklaracja funkcji. Mówi ona, że funkcja pobiera wartość całkowitą i zwraca napis. Ta konstrukcja jest równoważna ze switch znanym z innych języków. Program przechodzi przez wszystkie wszystkie linie i sprawdza zgodność. Ostatnia instrukcja jest odpowiednikiem default czyli jeżeli w poprzednich liniach komputer nie znajdzie dopasowania to zostanie wykonana ostatnia instrukcja. W Haskell każda funkcja musi zwrócić wartość. Rekurencja. Oczywiście zamiana pierwszej części z drugą skutkowała by tym, że pierwsza linia wykonywałaby się zawsze, a druga nigdy co skutkowało by tym, że obliczenia nigdy by się nie zakończyły (powiedzmy) W rekurencji, szczegółowe warunki powinny znaleźć się na początku listy, a później bardziej ogólne. W tym przypadku jeśli jako argument funkcji podamy h, wówczas Funkcja musi zwracać jakąś wartość, dlatego na końcu należałoby dodać instrukcję wyłapująca wszystkie możliwe wartości. Krotki Dodajmy do siebie dwa wektory Funkcje fst i snd zwracają odpowiedni pierwszy i drugi element krotki. Niestety, nie ma funkcji zwracającej trzeci czy dalszy element krotki(można stworzyć taką funkcję),jednak lepszym rozwiązaniem będzie: Krotki Funkcja zwracającą trzeci element krotki. _ - nie dbamy oto co jest w danym miejscu. Operacje na listach. Tworzenie list: ghci > [1,2,3] lub ghci > 1:2:3:[] –wynik będzie taki sam lista zawierająca listy: ghci > let a = (1:2:3:[]) ghci > let b = (4:5:6:[]) ghci > a:b:[] Tworzenie listy z sum elementów krotek: Operacje na listach. Tworzenie własnej Implementacji funkcji head Sprawdzanie(działanie): Operacje na listach. Tworzenie własnej Implementacji funkcji head Sprawdzanie(działanie): ghci> head' [] *** Exception: Can't call head on an empty list, dummy! ghci> head' [3,2,5] 3 ghci> head' "Hello" 'H' . Operacje na listach. Tworzenie własnej Implementacji funkcji head Pojawiła nam się teraz nowa funkcja error, która pobiera łańcuch znaków i generuje błąd wykonania. Łańcuch znakowy to informacja co poszło nie tak. Operacje na listach. Kolejna prosta funkcja, która zwróci nam dwa pierwsze elementy listy (zwraca komunikat jeśli lista jest pusta lub krótsza) Operacje na listach. Teraz użyjemy dopasowywania wzorca i rekurencji. Jeżeli funkcja dostanie pustą listę zwraca 0. Jeżeli lista nie jest pusta jest dzielona na głowę i ogon. Ogon przekazywany jest jako argument przy wywołaniu funkcji, a do tego co funkcja zwróci dodawane jest 1 ponieważ głowa ma zawsze długość 1. We wzorcu użyliśmy _ ponieważ nie ma dla nas znaczenia co zawiera pierwszy element listy. Operacje na listach. Tym razem będzie nas interesował pierwszy element listy. Podobna funkcja do poprzedniej, lecz teraz dodajemy głowę a nie odrzucamy ją. Operacje na listach. W tej funkcji all to zwykła zmienna której przypisywana jest wartość wyrażenia w nawiasach. ++ to funkcja łącząca listy. X jest w nawiasie kwadratowym, ponieważ musi wystąpić zgodność typów. capital2 :: String -> String capital2 "" = "Empty string, whoops!„ capital2 (x:xs) = "The first letter of " ++ [x] ++ xs ++ " is " ++ [x] Guards -jest podobny do case z C++ tylko inaczej zapisany i o poszerzonych możliwościach. (sprawdza nierówności i równości). Guards Inny prosty przykład: można to też zapisać tak: ghci> 3 `myCompare` 2 GT Wywoływaniu funkcji wygląda tak: `nazwa`. ` - ten sam klawisz co ~ Where Where dokonuje łączenia wyrażenia ze zmienną na końcu, a zmienna jest wykorzystywana w funkcji wcześniej. Where Można nadać etykiety (są to stałe) lub krócej: Zdefiniowaliśmy stałe w bloku where, teraz zdefiniujemy funkcję. Do funkcji przekazywana jest lista krotek zawierających wagę i wzrost. Funkcja zwraca listę wyników bmi. Let Podobne w działaniu do where jest let. Where na końcu funkcji łączy wyrażenie ze zmiennymi.(widoczne jest w całej funkcji). Let również łączy wyrażenie ze zmienną, ale działa bardzo lokalnie ( nie jest widoczne w całej funkcji). Let Zasada działania let: let <łączenia> in <wyrażenie> Zmienne zdefiniowane w części łączenia są dostępne w części wyrażenia. Let dokonuje łączenia wyrażenia ze zmienną na początku, a zmienna jest wykorzystywana w funkcji później. Let Aby użyć więcej zmiennych, należy oddzielić je średnikami Nie musimy pisać średnika za ostatnim łączeniem. Let Let można umieścić wewnątrz operacji na listach Do let możemy również dodać predykaty. ghci> calcBmis [(26, 1)] [26.0] ghci> calcBmis [(24, 1)] [] ghci> calcBmis [(25,1),(24, 1),(30,1)] [25.0,30.0] Można umieścić let wewnątrz operacji na listach Nie możemy użyć nazwy bmi w wyrażeniu (w, h) <- xs ponieważ bmi jest zdefiniowane w wyrażeniu let i nie jest widoczne poza let. Gdy używamy let w przetwarzaniu list pomijamy in, ponieważ widoczność nazw jest tu ograniczona. Jeżeli pominiemy in bezpośrednio GHCi nasza zmienna związana z danym wyrażeniem będzie widoczna dla całej sesji: If - wyrażenie, które prawdopodobnie można umieścić wszędzie (else jest wymagane). ghci> [if 5 > 3 then "Woo" else "Boo", if 'a'>'b' then "Foo" else "Bar"] ["Woo","Bar"] ghci> 4 * (if 10 > 5 then 10 else 0) + 2 42 Case Haskell(head) To samo z użyciem case: Case Wyrażenie(expression) jest porównywane ze wzorem(pattern),a gdy pasuje podawany jest wynik(result). Jednak gdy żaden wzór nie będzie pasował do wyrażenia, wówczas pojawi się błąd wykonania. Case Wszystkie możliwe przypadki są rozwiązane w ostatniej linii. Rozwiązanie alternatywne( z użyciem where). Koniec Autor: Tomasz Strzelecki