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

Podobne dokumenty