Přehled syntaxe Haskellu
Základní vlastnosti jazyka
- norma Haskell98, nepatrný update v Haskell 2010
- čistě funkcionální, referenční transparentnost
- bez implicitních side-efektů nebo přístupu ke globálním datům
- silně a staticky typovaný, s automatickým typováním
- líně vyhodnocovaný (implicitně)
- přehled obsahu některých standardních knihoven
- podrobnosti najdete přímo v normě (je celkem čitelná)
Názvy identifikátorů a operátorů
- identifikátor začíná písmenem a každý další znak je písmeno, číslice, podtržítko nebo apostrof (například
n_2'
) - názvy jsou case-sensitive, velikost prvního písmena mění význam:
- velké: typové konstruktory, třídy a datové konstruktory
- malé: typové proměnné, funkce a konstanty
- operátory jsou vždy tvořeny maximální posloupností nealfabetických znaků, operátory které jsou konstruktory musí začínat dvojtečkou (například
3*-2
selže na nedefinovaném*-
)
Definice funkcí a konstant
- definice konstant: vzor
=
výraz definuje všechny názvy obsažené ve vzoru - definice n-ární funkce: jméno vzor1… vzorn
=
výraz definice se strážemi:
jméno vzor1 … vzorn
|
stráž1=
výraz1
|
…
|
strážm=
výrazmdefinice pomocných funkcí a konstant po jejich použití:
definice_funkcewhere
definice_pomocné_funkce1
…
definice_pomocné_funkcemwhere
lze použít jen při top-level definici nebo zacase
, pomocné definice jsou společné pro všechny větve se strážemi a mohou být vzájemně rekurzivnídefinice pomocných funkcí a konstant před jejich použitím:
let
definice_pomocné_funkce1
…
definice_pomocné_funkcemin
výrazlet
výraz lze použít místo libovolného výrazu, pomocné definice mohou být vzájemně rekurzivnídefinice anonymní funkce (λ-funkce) je výraz
\
vzor1 … vzorn->
výraz, napříkladsquareList l = map (\x -> x*x) l
Stráže (guards)
- každá stráž v definicích funkcí nebo case-výrazech je seznam oddělený čárkami, pro úspěch musí uspět každá položka seznamu
- každá položka je jedno z:
- boolský výraz – nejčastější varianta, uspěje při vyhodnocení na
True
- let-deklarace (bez
in
) – zavede lokální funkci, uspěje vždy - vzor
<-
výraz (od Haskell 2010) – vyhodnotí výraz a zkusí výsledek navázat na vzor (zpřístupňuje nová jména)
- boolský výraz – nejčastější varianta, uspěje při vyhodnocení na
Vzory (patterns)
- název naváže (match) cokoliv a zpřístupní pod tímto názvem, žádný název se nesmí v jednom vzoru opakovat (není unifikace)
_
(podtržítko) jen naváže cokoliv- literál naváže jen shodnou hodnotu, například
12.3
nebo"text"
- název
@
vzor je jako vzor ale navíc zpřístupní celek pod novým názvem - Konstruktor vzor1 … vzorn naváže hodnotu vytvořenou daným n-árním konstruktorem, navíc se musí navázat jeho parametry na dané vzory.
- funguje i pro nulární konstruktory, například
True
- seznamy mají speciální konstruktory
[]
a vzorhlava:
vzortělo a[
vzor1,
…,
vzork]
- k-tice mají speciální konstruktor
(
vzor1,
…,
vzork)
- vzory lze vnořovat do sebe, například
(True:_):dalsi
je splněn jen pokud první prvek prvního seznamu jeTrue
- funguje i pro nulární konstruktory, například
- vzor Kons
{}
uspěje na hodnotách zkonstruovaných n-árním konstruktorem Kons, podobně také záznamové vzory ~
vzor (líný vzor) uspěje vždy a zkouší vázat až v okamžiku použití názvů definovaných vzorem (při selhání vyvolá výjimku, typicky chceme použít něco jiného)- aritmetické n+k vzory byly v Haskellu 2010 odstraněny (nepoužívat)
Podmíněné výrazy
if
vytváří obyčejný výraz a musí vždy obsahovat stejně otypované větve:if
bool_výrazthen
výrazelse
výrazcase
je vícecestnýif
s vázáním výsledku jednoho výrazu na různé vzory:case
výrazof
vzor1->
výraz1
…
vzorm->
výrazm
Mezi vzori a->
může navíc být seznam stráží|
stráži,1,
…,
stráži,k, navíc lze použítwhere
společné pro všechny větve.
Základní typy
data Bool = False | True
pro pravdivostní hodnotyInt
aInteger
pro omezená a neomezená celá čísla (v GHC máInt
typicky velikost ukazatele na platformě aInteger
je z knihovny GMP)Char
pro unicode znaky, literály'a'
(viz funkce vData.Char
)[
a]
pro seznam prvků typu atype String = [Char]
pro řetězce, literály"abcd"
(
a1,
…,
ak)
pro k-tice kde i-tý člen má typ ai- void-typ
()
, který může mít jen jednu hodnotu()
(je to 0-tice) Double
aFloat
pro klasická IEEE-754 číslapřidání
Nothing
jako indikátor neůspěchu:data Maybe a = Nothing | Just a
Typové deklarace
- výraz
::
typ je výraz se specifikovaným typem, například1 + (1::Integer)
- lze také pro více funkcí/konstant najednou jméno1
,
…,
jménok::
typ - deklarace může obsahovat kontext specifikující požadavky na třídy pro zmíněné typové proměnné, syntax jména
::
kontext=>
typ kde kontext je ve tvaru(
Třída1 params1,
…,
Třídak paramsk)
(závorky lze vynechat pokud k=1) - typová specifikace může vypadat jako:
- volná proměnná (začíná malým písmenem) za kterou lze dosadit libovolný typ společný pro všechny výskyty proměnné v dané typové specifikaci (typ musí splňovat kontext)
- funkční typ typvstupu
->
typvýstupu, asociuje doprava – napříklada -> (b -> c)
se závorkami i bez deklaruje typ funkce s dvěma parametry typůa
ab
a výsledkem typuc
- parametrizovaný typový konstruktor Typ_kon Param1 … Paramk kde Parami je libovolná typová specifikace, například
Maybe Int
nebo(Char,Float,Float)
nebo[Maybe a]
Definice typu
- typová synonyma pomocí
type
Typ_kon parametry = Specifikace_typu definují zaměnitelné typy (zkratky), napříkladtype String = [Char]
nebotype MaybeSez a = [Maybe a]
(type
nemůže zavádět nové datové konstruktory) newtype
definuje stejným způsobem jakotype
, akorát zavádí nový konstruktor a vzniká odlišný typ z hlediska typového systému – stejná reprezentace, ale nejdou zaměnit, napříkladnewtype Cislo = Cislo Int
Velmi často se používá konvence:
newtype Age = Age { unAge :: Int }
což nám dává dvě vzájemně inverzní funkce pro konverzi
Age :: Int -> Age unAge :: Age -> Int
Výhoda
newtype
oproti použitídata
s jedním konstruktorem je ve výkonu. Norma zaručuje že změna je jen na úrovni typu při kompilaci, reprezentace obou typů je identická a tedy konverze mezi nimi je prázdná operace.- úplně nový typ se definuje pomocí
data
Typ_kon typ_params = Dat_kon1 Params1|
…|
Dat_konk Paramsk, napříkladdata Maybe a = Nothing | Just a
nebodata Obrazec bod = Cara bod bod | Trojuhelnik bod bod bod
(zavedené datové konstruktory se chovají jako ni-ární funkce) - Paramsi může vždy být jedno z:
- posloupnost ni typů oddělených mezerami (ni ≥ 0) kde jednotlivé položky jsou identifikovány pozičně (typy lze definovat i rekurzivně)
- záznam (record) tvaru
{
typ_dekli,1,
…,
typ_dekli,n}
kde typ_dekln,i je tvaru jména::
typ
data
anewtype
definice můžou na konci specifikovat seznam tříd pro které jsou automaticky vygenerovány instance, syntaxe je typ_defderiving (
Třída1,
…,
Třídam)
, ale norma to zaručuje jen pro některé třídy:Eq
,Ord
,Enum
,Bounded
,Show
,Read
,Ix
(závorky lze vynechat pokud m = 1)- infixní typové a datové konstruktory, typové proměnné, nebo třídy – musí začínat znakem
:
(seznamová:
je tedy speciální instancí tohoto pravidla) - TODO:
- anotace striktnosti (možná jinam, s bang patterns?)
Záznamy
jména zavedená záznamovými konstruktory jsou globální funkce pro výběr položky, například definování
data Barva = RGB {r,g,b :: Int} | CMY {c,m,y :: Int}
odpovídá (mimo jiné) definici globálních parciálních funkcí
r,g,b,c,m,y :: Barva -> Int
. Příklad polymorfního typu pro binární stromy:data Strom l v = List l | Vrchol { levy, pravy :: Strom l v, hodnota :: v }
- nová hodnota lze zkonstruovat:
- použitím konstruktoru jako funkce s pozičními parametry
RGB, CMY :: Int -> Int -> Int -> Barva
, napříkladcerna = RGB 0 0 0
- s použitím názvů:
RGB {g=0, b=128}
, nezmíněné položky dostanou hodnotuundefined
- pozměněním existující hodnoty:
modra = cerna {b=255}
- použitím konstruktoru jako funkce s pozičními parametry
analogickou syntaxi lze používat jako vzory pro pattern matching, například
CMY { c=0, m=mVal }
se naváže pokud byla hodnota zkonstruována pomocíCMY
, ve složcec
je0
a do lokálníhomVal
se uloží hodnota složkym
List comprehensions
- zápis
[
výraz|
spec1,
…,
speck]
kde speci je vždy buď boolská stráž, let-deklarace, nebo generátor tvaru jménoi<-
sez_výrazi - ve výsledném seznamu jsou právě ty instance výraz kde proměnné jsou libovolně zvolené ze svých generujících seznamů a stráže jsou splněné
- existují různá rozšíření – paralelní generování (místo
zip
), SQL-like syntaxe
Operátory
(
operátor)
je funkce, například(++) "abc" "def" == "abc" ++ "def"
`
funkce`
je infixní operátor, například10 `div` 3 == div 10 3
- parciální aplikace binárního operátoru má syntaxi
(
par1 op)
a(
op par1)
, například(`mod` 3)
počítá modulo třemi a(10 /)
dělí deset čímkoliv - infixní operátory mají prioritu 0-9 a asociativitu která rozhoduje o závorkování při setkání operátorů se stejnou asociativitou (levá, pravá, žádná)
- deklarace fixity číslo op1
,
…,
opk kde fixity jeinfixl
neboinfixr
neboinfix
- nejvyšší prioritu 10 má aplikace funkce, defaultní priorita je
infixl 9
- prioritu lze definovat i pro infixní použití funkcí nebo datových konstruktorů:
infixl 7 `div`, `mod`
- infixní konstruktory jsou právě operátory začínající dvojtečkou, seznamová
:
je speciální instance tohoto pravidla
Typové třídy
- typová třída odpovídá interface v objektově-orientovaném programování (ne třídě v OOP!), tedy specifikuje funkce které musí být definované na všech typech dané třídy
- definice třídy pomocí
class
Třída typ_promwhere
specifikace_funkcí kde specifikace může obsahovat- typové signatury využívající typovou proměnnou typ_prom
- defaultní implementace definovaných funkcí (typicky využívající jiné funkce z té samé nebo nadřazené třídy)
class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y)
- funkce definované třídou jsou globální a fungují na libovolných typech dané třídy
- aby mohl být typ zařazen do třídy, je nutné definovat jeho instanci třídy pomocí
instance
Třída Typwhere
definice_funkcí definice třídy nebo instance může navíc obsahovat před názvem třídy kontext specifikující požadavky na třídy pro použitou typovou proměnnou (tím se vytváří acyklická hierarchie tříd)
instance Eq a => Eq (Maybe a) where Nothing == Nothing = True Just x == Just y = x == y _ == _ = False
- viz typové deklarace a definice: specifikace kontextu, automatická derivace instancí
v praxi se často používají rozšíření pro víceparametrické třídy (více proměnných, například
IArray a e
), někdy dokonce se závislostmi mezi proměnnými – to dělá třídy silnější než podobné mechanismy z imperativních jazyků