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, tutaj jest ten link do podręcznika).
Pozwala to na pewnego rodzaju metaprogramowanie na poziomie typów.
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]
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 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
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
==,<=
? 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
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
myTree :: Tree Int myTree = Node 3 (Node 1 Empty (Node 2 Empty Empty)) (Node 4 Empty Empty)