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*-2selž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é_funkcemwherelze 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é_funkceminvýrazletvý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.3nebo"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:_):dalsije 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
ifvytváří obyčejný výraz a musí vždy obsahovat stejně otypované větve:ifbool_výrazthenvýrazelsevýrazcaseje vícecestnýifs vázáním výsledku jednoho výrazu na různé vzory:casevý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žítwherespolečné pro všechny větve.
Základní typy
data Bool = False | Truepro pravdivostní hodnotyIntaIntegerpro omezená a neomezená celá čísla (v GHC máInttypicky velikost ukazatele na platformě aIntegerje z knihovny GMP)Charpro 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) DoubleaFloatpro klasická IEEE-754 číslapřidání
Nothingjako 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ůaaba výsledkem typuc - parametrizovaný typový konstruktor Typ_kon Param1 … Paramk kde Parami je libovolná typová specifikace, například
Maybe Intnebo(Char,Float,Float)nebo[Maybe a]
Definice typu
- typová synonyma pomocí
typeTyp_kon parametry = Specifikace_typu definují zaměnitelné typy (zkratky), napříkladtype String = [Char]nebotype MaybeSez a = [Maybe a](typenemůže zavádět nové datové konstruktory) newtypedefinuje 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 IntVelmi č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 -> IntVýhoda
newtypeoproti použitídatas 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í
dataTyp_kon typ_params = Dat_kon1 Params1|…|Dat_konk Paramsk, napříkladdata Maybe a = Nothing | Just anebodata 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
dataanewtypedefinice 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žcecje0a do lokálníhomValse 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 jeinfixlneboinfixrneboinfix - 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í
classTřída typ_promwherespecifikace_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í
instanceTřída Typwheredefinice_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ů