APP-Notes

Dag 3 - Meer language features

Tuples

Tuples zijn een veelgebruikt returntype in Haskell. Tuples bieden de mogelijkheid om twee values in een object te ‘wrappen’. Vervolgens kan dit object weer uitgepakt worden. Bijvoorbeeld: (10, 20) is een tuple met de waardes 10 en 20. Om dit object vervolgens uit te pakken, kunnen de functies fst en snd. fst geeft hierbij de eerste waarde terug en snd de tweede waarde.

Recursion

Haskell kent geen loops. Hierdoor wordt er veel gebruik gemaakt van recursion. Op het eerste gezicht lijkt dit hetzelfde te werken als dat ik gewend ben in Java. Wel lees ik dat Haskell veel handige functies heeft, waardoor het werken met recursie makkelijker wordt. Functies als head, tail en last voor het gebruik van lijsten bieden een gemakkelijk gebruik van lijsten. Zo kan er bijvoorbeeld steeds de tail van parameter x meegegeven worden aan een recursieve aanroep tot de lijst leeg is.

head, tail & last

Functions

Naast wat simpelere expressies, is het ook mogelijk om functies te definieren. Hiervoor gebruik je in Haskell het = teken. Een functie ziet er bijvoorbeeld uit als doBar p = p * 100. De naam van deze functie is doBar. De functie heeft een parameter p. Wanneer deze aangeroepen wordt, wordt de parameter vermenigvuldigd met 100. doBar 20 produceert het antwoord 2000.

Functies 1

Het type van p wordt door Haskell herkend als een a en de functie geeft een a terug. Ook zien we dat a van het type Num moet zijn, omdat dit aangegeven staat als Num a => .... Na wat research zie ik dat Num het basistype is voor alle numerieke waardes. Blijkbaar heeft Haskell dus inheritance. Meer hierover later in deze blog.

Type signatures

In het bovenstaande kopje hebben we gezien hoe functies werken. In het voorbeeld zocht Haskell zelf uit het functietype is en wat het return type is. Dit kan ook meegegeven worden aan de functie, zodat dit compile-time bekend is. Dit is leesbaarder, maar zorgt ook voor minder bugs, omdat het altijd helder is welke types er gebruikt worden. Dit wordt als volgt aangegeven:

doBar :: Int -> Int
doBar p =
    p * 100

Hiermee specificeren we dat p van het type Int moet zijn en dat er een Int teruggegeven moet worden. In het vorige kopje zagen we dat basetypes aangegeven kunnen worden doormiddel van =>. Om te specificeren dat p van het (basis)type Num moet zijn, kunnen we dit opschrijven als:

doBar :: Num a => a -> a
doBar p =
    p * 100

Pattern matching

Een functionaliteit die bij Haskell al snel naar boven komt, is pattern matching. Pattern matching wordt gebruikt om een resultaat terug te geven bij een bepaalde conditie. Voor lijsten kan het bijvoorbeeld gebruikt worden om bij een lege lijst een bepaalde input terug te geven. Als voorbeeld hebben we de functie doBar die een gegeven lijst muteert op basis van pattern matching. Het volgende willen we doen:

doBar :: [Int] -> [Int]
doBar [] = [42]
doBar [first] = first * 2
doBar items = concat [items, [168]]

De concat functie voegt meerdere lijsten samen. De signature van deze functie is concat :: [[Int]] -> [Int].

Operators

Bij pattern matching wordt er veel gebruik gemaakt van operators. Zo kwam ik de : operator tegen. Deze operator wordt gebruikt om te pattern-matchen op lijsten. Deze operator deelt de lijst op in een head item en de rest van de items: doBar (first:rest). Ook kan de : operator gebruikt worden om een item aan een lijst te knopen: items = 10:[20,30,40].

Een volledig lijstje is hier te vinden.

Guards

Een andere gave feature van Haskell zijn guards. Deze zijn een korte manier om case-constructies te schrijven. Een voorbeeld van zo’n guard:

doBar n 
    | n == 0 = "Nul!" 
    | otherwise = "Geen nul!"

De bovenstaande functie controleert of n 0 is. Zo ja, wordt de String “Nul!” teruggegeven. Zo nee, wordt de String “Geen nul!” teruggegeven.