Základem je dokumentace a tutoriál.
Haskell podporuje modulární programování — každý program se může sestávat z modulů a podmodulů, které spolu vzájemně spolupracují. To napomáhá udržovatelnosti kódu, proto se u větších projektů bez rozdělení do modulů těžko obejdeme.
Pokud chceme nějaký modul využít, musíme ho zavést pomocí klíčového slova import. Zavedené moduly se musí nacházet ve zdrojovém kódu před definicemi našich funkcí a každý takový import musí být uveden na samostatném řádku. Příklad zavedení modulu pro práci se seznamy:
import Data.List
Pokud nám názvy funkcí z modulů kolidují, máme dvě možnosti: skrýt jednu z funkcí nebo zadávat plné jméno jedné nebo obou funkcí. Skrytím funkce z jednoho modulu můžeme bez problémů používat přímo funkci z modulu druhého. Příklad nahrazení funkce length
za více obecnou (výpočet aritmetického průměru číselné řady od 1 po 1000):
import Prelude hiding (length)
import Data.List (genericLength)
length :: (Num i) => [b] -> i
length = genericLength
main :: IO ()
main = print $ sum x / length x
where x = [1..1000]
První příkaz importuje celý modul Prelude
kromě funkce length
, druhý příkaz importuje pouze funkci genericLength
z modulu Data.List
, pomocí které si můžeme předefinovat funkci length
, aniž bychom měnili zbylý kód.
V případě, kdy chceme využívat více funkcí se stejným názvem, můžeme celý modul importovat kvalifikovaně, tedy nastavit mu menší prioritu. Kupříkladu modul Data.Set obsahuje funkci map
, která pracuje s množinami namísto se seznamy. Při použití této funkce bychom museli zadat plný název (tedy Data.Set.map
). Kvalifikovaný import provedeme takto:
import qualified Data.Set
Pokud se nám celá cesta nechce vypisovat, není problém název modulu zkrátit:
import qualified Data.Set as S
Pak bychom k funkci map
pro množiny přistupovali napsáním S.map
.
Dle konvence bychom měli názvy modulů začínat velkým písmenem a při rozdělování do adresářů názvy oddělovat tečkou. Pokud např. v adresáři Modul
budeme mít soubor Podmodul.hs
, do tohoto souboru napíšeme:
module Modul.Podmodul
( funkce1
, funkce2
...
, funkcex
) where
...
Do závorek specifikujeme názvy funkcí, které se mají exportovat. V mnohém se to podobá rozhraní a viditelnosti funkcí z objektově-orientovaných jazyků. Pokud chceme exportovat i nějakou datovou strukturu, napíšeme za její název dvě tečky obklopené závorkami, což zpřístupní i všechny její konstruktory:
module Modul.Podmodul
( Struktura(..)
, funkce1
, funkce2
...
) where
data Struktura = Konstruktor
...
V Haskellu se typy dají dělit do tříd. Ty slouží ke sloučení typů s podobnými vlastnostmi. Například všechna čísla patří do třídy Num
a rozdělují se do několika dalších podtříd (Integral
pro celá čísla, Rational
pro zlomky, Fractional
pro desetinná čísla, Floating
pro čísla s plovoucí desetinnou čárkou a další). Kromě jednoduchého rozdělení do kategorií mohou být vlastnosti specifičtějšího charakteru, třeba uspořádatelnost (typová třída Ord
), porovnatelnost (typová třída Eq
), nebo převoditelnost na řetězec (typová třída Show
). Tyto vlastnosti se dají k danému datovému typu připojit klíčovým slovem deriving
a jejich chování definovat pomocí klíčového slova instance
. Samotná typová třída se vytváří pomocí klíčového slova class
. Příklad připojení vlastností k datovému typu:
data Grayscale = Black | Gray | White deriving (Show, Read)
Takto definovaný datový typ (výčet tří barev) můžeme převádět na řetězec a z řetězce pomocí funkcí show :: (Show a) => a -> String
a read :: (Read a) => String -> a
:
> show Gray "Gray" > read "Gray" <interactive>:1:0: Ambiguous type variable `a' in the constraint: `Read a' arising from a use of `read' at <interactive>:1:0-10 Probable fix: add a type signature that fixes these type variable(s) > read "Gray" :: Grayscale Gray
Funkce read
potřebuje znát datový typ, do kterého řetězec převádíme.
module Data.Stack
( Stack(..)
, stack
, empty
, push
, pop
, toList
, fromList
) where
data Stack a = Stack [a] deriving (Show, Read, Eq, Ord)
stack :: Stack a
stack = Stack []
empty :: Stack a -> Bool
empty (Stack xs) = null xs
push :: a -> Stack a -> Stack a
push x (Stack xs) = Stack (x:xs)
pop :: Stack a -> (a, Stack a)
pop (Stack (x:xs)) = (x, Stack xs)
pop (Stack _) = error "The stack is empty."
toList :: Stack a -> [a]
toList (Stack xs) = reverse xs
fromList :: [a] -> Stack a
fromList xs = Stack (reverse xs)
module Data.Queue
( Queue(..)
, queue
, empty
, enqueue
, dequeue
, toList
, fromList
) where
data Queue a = Queue [a] [a]
deriving (Show, Read, Eq, Ord)
queue :: Queue a
queue = Queue [] []
empty :: Queue a -> Bool
empty (Queue xs ys) = null xs && null ys
enqueue :: a -> Queue a -> Queue a
enqueue x (Queue xs ys) = Queue (x:xs) ys
dequeue :: Queue a -> (a, Queue a)
dequeue (Queue [] []) = error "The queue is empty."
dequeue (Queue xs []) = dequeue (Queue [] (reverse xs))
dequeue (Queue xs (y:ys)) = (y, Queue xs ys)
toList :: Queue a -> [a]
toList (Queue xs ys) = xs ++ reverse ys
fromList :: [a] -> Queue a
fromList xs = Queue xs []