====== 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)