3.1. LISTAT 35 destaan pisteittäisesti: init :: [α] [α] init (x : []) = [] init (x : xs) = x : init xs Varuskirjastoon kuuluu myös funktiot take ja drop, jotka ottavat tai tiputtavat pois, funktiosta riippuen, parametrina annetun määrän alkioita annetun listan alusta: take :: Int [α] [α] take _ [] = [] take n xs n < 0 = error "Negatiivinen taken parametri" n == 0 = [] otherwise = x : take (n 1) xs (Drop määritellään samaan tyyliin.) Funktio zip, joka kuuluu varuskirjastoon, vetää vetoketjun kiinni ottamalla kaksi listaa ja pareittain yhdistämällä ne parien listaksi: zip :: [α] [β] [(α, β)] zip (x : xs) (y : ys) = (x, y) : zip xs ys zip = [] Varuskirjastossa on myös sen käänteisfunktio unzip :: [(α, β)] ([α], [β]). Huomautus 5 Huomaa, kuinka jo monien funktioiden tyypeistä voi varsin hyvin päätellä, mitä ne tekevät: concat :: [[α]] [α] ( ) :: (β γ) (α β) (α γ) zip :: [α] [β] [(α, β)] unzip :: [(α, β)] ([α], [β]) Tämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.
36 LUKU 3. LISTANKÄSITTELYÄ Hyödylliseksi on osoittautunut zipin variantti, jossa parikonstruktorin tilalle voi laittaa haluamansa (sopivantyyppisen) funktion: zipwith :: (α β γ) [α] [β] [γ] zipwith f (x : xs) (y : ys) = f x y : zipwith f xs ys zipwith _ = [] Tällöin zipkin voi saada toisenlaisen muodon: zip :: [α] [β] [(α, β)] zip = zipwith (, ) Lopuksi vielä esitellään varuskirjastoon kuuluva listan indeksointioperaattori: (!!) :: [α] Int α (x : xs)!! n n < 0 = error "Negatiivinen (!!):n parametri" n == 0 = x otherwise = xs!! n 3.2 Lista-aiheisista todistuksista Toisinaan on hyvä voida todistaa, että tietyllä funktiolla on tietty ominaisuus, ihan luotettavuuden parantamisen kannalta. Esimerkiksi voisi olla hyvä todistaa yhtälö (xs ++ ys) ++ zs = xs ++(ys ++ zs). Tällaiset todistukset etenevät parhaiten induktiolla. Listan yli tapahtuvassa induktiossa on kolme askelta; todistaakseen, että kaikilla listoilla on jokin ominaisuus, pitää todistaa seuraavat asiat: Perustapaus P( ) Todista, että ko. ominaisuus on (listatyyppisellä) pohjalla. Perustapaus P([]) Todista, että ko. ominaisuus on tyhjällä listalla. Induktioaskel Todista, että jos ko. ominaisuus on mielivaltaisella listalla xs niin silloin kaikilla (tyypiltään sopivilla) alkioilla x ko. ominaisuus on listalla x : xs. Jos jättää todistamatta pohjan perustapauksen mutta todistaa muut, tulee ominaisuus todistettua vain äärellisille listoille; jos jättää todistamatta tyhjän listan perustapauksen mutta todistaa muut, tulee ominaisuus todistettua vain osittaislistoille.
3.3. MUUTAMIA LISTAFUNKTIONAALEJA 37 Todistetaan yhtälö induktiolla xs:n suhteen: (xs ++ ys) ++ zs = xs ++(ys ++ zs) Perustapaus P( ) ( ++ ys) ++ zs = ++ zs = = ++(ys ++ zs). Perustapaus P([]) ([] ++ ys) ++ zs = ys ++ zs = [] ++(ys ++ zs). Induktioaskel Tehdään induktio-oletus, että mielivaltaisella listalla xs pätee (xs ++ ys) ++ zs = xs ++(ys ++ zs). Olkoon x mielivaltainen tyypiltään sopiva alkio. Tällöin pätee ((x : xs) ++ ys) ++ zs = (x : (xs ++ ys)) ++ zs = x : ((xs ++ ys) ++ zs) = x : (xs ++(ys ++ zs)) = (x : xs) ++(ys ++ zs). 3.3 Muutamia listafunktionaaleja Funktio-ohjelmoinnin yksi merkittävimmistä yksittäisistä keinovaroista on funktionaalit. Funktionaaleilla voidaan abstrahoida erilaisia tietorakenteiden läpikäyntejä kuten tee tämä kaikille listan alkioille (map) tai muodosta lista antamalla tälle operaattorille näiden listojen alkiot pareittain operandeiksi (zipwith). Huomautus 6 Funktionaali ( functional mutta tavallisemmin higher-order function, HOF) on funktio, jonka yksi tai useampi parametri on funktio. Koska kaikki Haskellin funktiot ovat curry ttuja, voidaan ajatella, että mikään funktio ei oikeastaan koskaan palauta funktiota, toisinaan funktioita vain kutsutaan vajaalla parametrilistalla. Toisaalta samasta syystä kaikki moniparametriset funktiot toimivat palauttamalla funktion. Näin ollen ei ole mieltä määritellä, että funktionaali olisi funktio, jonka yksi tai useampi parametri tai paluuarvo on funktio. Funktionaali map, joka kuuluu varuskirjastoon, määritellään seuraavasti: map :: (α β) [α] [β] map f [] = [] map f (x : xs) = f x : map f xs Esimerkki 20 map (λn n + 1) [4, 2, 9, 4, 5] = [5, 3, 10, 5, 6]
38 LUKU 3. LISTANKÄSITTELYÄ Funktionaalille map pätee muutama yhtälö (tod. harj.): map id = id (3.1) map (f g) = map f map g (3.2) f head = head map f kun f = (3.3) map f tail = tail map f (3.4) map f reverse = reverse map f (3.5) map f concat = concat map f (3.6) Huomautus 7 Funktioiden yhtäsuuruus määritellään tavallisesti ekstensionaalisesti, jolloin f = g pätee jos ja vain jos f x = g x pätee kaikilla tyypiltään sopivilla lausekkeilla x. Toinen määrittelyvaihtoehto on intensionaalinen, jossa funktioiden yhtäsuuruuteen vaikutttaa jokin muukin tekijä kuin niiden pisteittäinen yhtäsuuruus esimerkiksi se, ovatko ne peräisin samasta funktiomäärittelystä, tai funktioiden ohjelmakoodin osoite muistissa. Tässä monisteessa noudatamme ekstensionaalista määrittelyä. Huomaa, että vaikka määrittelemme näin funktioiden yhtäsuuruuden, silti operaattoria (==) ei ole funktiotyypeille määritelty, sillä funktioiden ekstensionaalisen yhtäsuuruuden testaamiselle ei ole yleispätevää algoritmia. Toinen hyödyllinen funktionaali on filter: filter :: (α Bool) [α] [α] filter p [] = [] filter p (x : xs) p x = x : filter p xs otherwise = filter p xs Se tuottaa annetusta listasta listan, josta on raakattu pois ne alkiot, jotka eivät täytä annettua ehtoa. Esimerkki 21 1. filter even [1, 2, 4, 5, 32] = [2, 4, 32] 2. let { square x = x x } in (sum map square filter even)[1..10] = 220 Huomautus 8 Funktio even :: Integral α α Bool kuuluu varuskirjastoon. Erityissyntaksi [1..10] edustaa kokonaislukujen listaa väliltä 1 10.
3.3. MUUTAMIA LISTAFUNKTIONAALEJA 39 Edellisessä esimerkissä esiintyi varuskirjastoon kuuluva funktio sum :: Num α [α] α. Se voidaan määritellä esimerkiksi näin: sum :: Num α [α] α sum [] = 0 sum (x : xs) = x + sum xs Vastaavalla tavalla voitaisiin määritellä esimerkiksi myös prod: prod :: Num α [α] α prod [] = 1 prod (x : xs) = x prod xs Nämä kaksi määritelmää ovat saman suunnittelumallin tapauksia; se malli voidaan esittää esimerkiksi näin, kun h on määriteltävä funktio, e on sen arvo tyhjän listan tapauksessa ja ( ) on määritelmässä käytettävä operaattori: h [] = e Funktio h muuttaa listan h (x : xs) = x h xs x 1 : (x 2 : (x 3 : (x 4 : []))) arvoksi x 1 (x 2 (x 3 (x 4 []))). Tämä määrittelykaava voidaan esittää funktionaalina foldr: foldr :: (α β β) β [α] β foldr f e [] = e foldr f e (x : xs) = x f (foldr f e xs) Tämä funktionaali kuuluu toki varuskirjastoon. Se on ehkä tärkein yksittäinen listafunktio(naali), sillä sen avulla voidaan melkein kaikki muut listafunktiot määritellä: concat = foldr (++) [] reverse = foldr (λ x xs xs ++ x) [] length = foldr (λ _ n n + 1) 0 sum = foldr (+) 0 prod = foldr ( ) 1 map f = foldr ((:) f) []
40 LUKU 3. LISTANKÄSITTELYÄ Kaikkia listafunktioita ei kuitenkaan voi määritellä foldr:n pohjalta. Yksi tällainen esimerkki on zip. Aina foldr-määritelmä ei ole tehokkain mahdollinen; esimerkiksi yllä annettu reversen määritelmä on asymptoottiselta aikavaativuudeltaan neliöllinen, kun aiemmin annettiin lineaariaikainen määritelmä. Määritelläänpä nyt toisenlainen versio samasta ideasta: foldl :: (α β α) α [β] α foldl f e [] = e foldl f e (x : xs) = foldl f(z f x) xs Assosiatiivisilla operaattoreilla ( ) pätee foldr ( ) = foldl ( ) (harjoitustehtävä!). Jos operaattori ei ole assosiatiivinen, foldr ja foldl antavat eri tulokset, sillä ne ryhmittelevät laskun eri lailla: foldr ( ) e [x 1, x 2, x 3 ] = x 1 (x 2 (x 3 e)) foldl ( ) e [x 1, x 2, x 3 ] = ((e x 1 ) x 2 ) x 3 Funktionaalilla foldl onnistuu nyt reversen tehokas määrittely: Myös foldl kuuluu varuskirjastoon. reverse = foldl (flip (:)) [] Huomautus 9 Funktionaali flip määritellään varuskirjastossa seuraavasti: flip :: (α β γ) β α γ flip f x y = f y x Varuskirjastossa määritellään myös funktionaalit foldr1, foldl1 :: (α α α) [α] α, jotka eivät käsittele tyhjän listan tapausta vaan ne lopettavat rekursionsa yhden alkion listan tapaukseen ne eivät myöskään siten tarvitse tyhjän listan tapauksen paluuarvoa parametrikseen. Ne ovat hyödyllisiä silloin, kun tyhjän listan tapaukselle ei ole olemassa mitään järkevää vastausta. Esimerkki 22 Varuskirjaston funktiot maxlist ja minlist määritellään seuraavasti: maximum, minimum :: Ord α [α] α maximum = foldl1 max minimum = foldl1 min (Funktiot max ja min ovat tyyppiluokan Ord metodeja.)
3.4. ÄÄRETTÖMISTÄ LISTOISTA 41 3.4 Äärettömistä listoista Äärettömällä listalla tarkoitetaan lauseketta, jolla ei ole normaalimuotoa ja jonka yleisin mahdollinen tyyppi on jokin listatyyppi. Esimerkki 23 Seuraavat ovat äärettömiä listoja: 1. let { ones = 1 : ones } in ones 2. let { nats = 0 : map (λn n + 1) nats } in nats 3. let { fib = 1 : 1 : map (uncurry (+))(zip fib (tail fib)) } in nats Edellisen esimerkin kaikkien lausekkeiden arvo on, koska niillä ei ole normaalimuotoa. Toisaalta esimerkiksi ensimmäisen lausekkeen arvo on myös 1 :, koska (:) on löyhä; esimerkiksi head (1 : ) = 1. Samalla tavalla voidaan sanoa, että sen arvo on 1 : 1 : tai 1 : 1 : 1 : ja niin edelleen. Dana Scottin semanttisten alueiden teoriassa 3 jokaiseen tietotyyppiin eli semanttiseen alueeseen liittyy vertailuoperaattori, joka vertaa alueen arvojen (lausekkeiden) informaatiosisältöä, tai oikeastaan laskennan määrää. Jokaisessa alueessa on tämän suhteen pienin alkio, jota merkitään ; siinä ei ole yhtään informaatiosisältöä. Listatyypissä on voimassa vertailuketju 1 : 1 : 1 :. Voidaan ajatella, että tämän ketjun päässä on ketjun raja-arvo, joka on ääretön lista. 3. Ks. esim. OKP:n syksyn 2002 moniste.