====== Typy ====== ===== Wprowadzenie ===== W Haskellu mamy do czynienia z typowaniem statycznym. Oznacza to, ze już podczas kompilacji typ każdego z wyrażeń jest znany. W odróżnieniu jednak od standardowych języków programowania (np. Java), Haskell posiada mechanizm inferencji typów, co pozwala kompilatorowi wywnioskować typy wyrażeń, np: ghci> :t 'a' 'a' :: Char ghci> :t True True :: Bool ghci> :t "HELLO!" "HELLO!" :: [Char] ghci> :t (True, 'a') (True, 'a') :: (Bool, Char) ghci> :t 4 == 5 4 == 5 :: Bool Podobnie w przypadku funkcji, chociaż tutaj zaleca się stosowanie deklaracji explicite, chyba że tworzymy bardzo proste funkcje, np: mycomp a b = a < b ghci> :t mycomp ghci> Ord a => a -> a -> Bool Widzimy tutaj ciekawą rzecz. Sygnatura funkcji została poprawnie rozwinięta, ale nie mamy tutaj żadnego konkretnego typu, znanego z innych języków programowania (Int, Float, Double). Zamiast tego mamy ''%%a -> a -> Bool%%''. Jedynie typ wynikowy wygląda znajomo. Po lewej stronie tego wyrażenia, tuż przed ''%%=>%%'' pojawia się tzw. ograniczenie klasy ''Ord a'' wymuszające, że typ wejścia dla tej funkcji musi implementować //interfejs// ''Ord''. Do poczytanie o ograniczeniach było na Lab 2 (dla przypomnienia, [[http://learnyouahaskell.com/types-and-typeclasses|tutaj jest ten link do podręcznika]]). Pozwala to na pewnego rodzaju metaprogramowanie na poziomie typów. ==== Definiowanie własnych type-classes ==== Możemy oczywiście definiować własne //interfejsy// dla typów, czyli type-classy i dziedziczyć istniejące. Co więcej, mozna je aplikować do istniejących typów. {-# LANGUAGE FlexibleInstances #-} class Listable a where toList :: a -> [Int] instance Listable Int where toList x = [x] instance Listable Bool where toList True = [1] toList False = [0] ==== Definiowanie własnych typów ==== W Haskellu robimy to za pomocą słowa kluczowego data. Na przykład, aby zdefiniować typ opisujący punkt w przestrzeni 2D: data NamedPoint = NamedPoint { pointName :: String , pointX :: Int , pointY :: Int } Z takim type niewiele da sie zrobić. Nie da sie go nawet wyświetlić, ponieważ aby być wyświetlanym typ musi //implementować interfejs// Show, czyli dziedziczyć go. Na szczęście możliwe jest dziedziczenie typów. data NamedPoint = NamedPoint { pointName :: String , pointX :: Int , pointY :: Int } deriving (Show) ==== Parametryzowane Typy Danych ==== Parametryzowane typy danych w Haskellu to konstruktory polimorficznych typów. Typy takie mogą przechowywać wartości wielu różnych typów. Przykładem takiego typu jest typ Maybe, definiowany jako data Maybe a = Nothing | Just a Pozwala tworzyć pewnego rodzaju wrapper wokół typu, który w zależności od tego jak pójdą obliczenia, //wyrzuci// ten typ, albo Nothing, ale nadal interpretowany jako Maybe. maybe_sqrt :: Float -> Maybe Float maybe_sqrt x | x >= 0 = Just (sqrt x) | otherwise = Nothing ===== Zadania ===== - Wykorzystując wiedzę o tym jak tworzyć typeclassy, stwórz jedną o nazwie Intable, która pozwoli na konwersję [Char] to Int poprzez funkcję toInt. Użyj jej jako ograniczenie w funkcji mySuperAdd: mySuperAdd :: (Intable a, Intable b) => a -> b -> Int mySuperAdd x y = toInt x + toInt y ghci> mySuperAdd "123" "12" 135 ghci> mySuperAdd "123" (12::Int) 135 - Zbuduj nowy typ Osoba, zawierający imię, nazwisko, pesel. Napisz typeclase umożliwiającą porównywanie osób (po peselu) i sortowanie po nazwisku. Czy da się wyświetlić osobę? Co gdyby dziedziczyć po Eq i Ord? Jak zachowywałoby się porównywanie ''%%==,<=%%''? Poniższe powinny działać poprawnie: let szymon = Osoba "Szymon" "Bobek" "12345678901" let bobek = Osoba "S" "Bobek" "12345678901" let zenon = Osoba "Zenon" "Adamczyk" "111111111" ghci> szymon == bobek True ghci> szymon > zenon True - Zaimplementuj dwuargumentową funkcję ''find'', która jako argumenty przyjmuje listę oraz predykat. Funkcja ma zwrócić pierwszy element opakowany typem ''Maybe'', który spełnia dany predykat (predykat = funkcja zwracająca wynik typu ''bool''). Jeżeli takiego elementu nie ma, zwracane jest ''Nothing'' - Zaimplementuj drzewo binarne umożliwiające przechowywanie dowolnych typów, tak aby dało sie stworzyć je w następujący sposób: myTree :: Tree Int myTree = Node 3 (Node 1 Empty (Node 2 Empty Empty)) (Node 4 Empty Empty) - insert (wstawienie elementu) - empty (sprawdzanie czy drzewo jest puste) - search (sprawdzanie czy element jest w drzewie) - toString (wypisującą drzewo w postaci „a(b(d,e),c(,f(g,)))” ) - leaves ( zwracającą listę liści ) - nnodes (podającą ilość węzłów) - nsum (zliczającą sumę wartości w węzłach) - remove (usuwanie elementu)