815338A Ohjelmointikielten periaatteet 2014-2015. Harjoitus 7 Vastaukset Harjoituksen aiheena on funktionaalinen ohjelmointi Scheme- ja Haskell-kielillä. Voit suorittaa ohjelmat osoitteessa https://ideone.com/ tai http://www.tutorialspoint.com/codingground.htm. Tehtävä 1. Suorita Schemellä seuraavat laskutoimitukset 5-6 1/5 + 3/10 1.5 * (21.2-11.0)/3.2 Tuloksen näyttäminen onnistuu funktiolla display, esimerkiksi 12*13 ja rivinvaihto voitaisiin kirjoittaa (display (* 12 13)) Vastaus. Schemessä operaatiot kirjoitetaan listojen muodossa samaan tapaan kuin funktiotkin. Ensimmäiseksi tulee operaatio ja sitten argumentit. Mainitut laskutoimitukset kirjoitettaisiin (tuloksen näyttämisen kera) seuraavasti: (display (- 6 5)) (display (+ (/ 1 5) (/ 3 10))) (display (* 1.5 (/ (- 21.2 11.0) 3.2))) Kun lausekkeet ajetaan tulkissa, saadaan vastaukset 1 1/2 4.781249999999999 Tehtävä 2. Tutki seuraavia funktioita: a) Scheme-funktio (define (a_fun x y) (cond ((null? x) y) (else (cons (car x) (a_fun (cdr x) y))))) b) Haskell-funktio b_fun(x,[]) = [] b_fun(x,(y:ys)) = if x==y then (y:ys) else b_fun(x,ys)
Yritä ensin päätellä koodista funktioiden toiminta. Kopioi koodi tulkkiin ja kutsu funktiota; tarkista oliko päätelmäsi oikea. Scheme-funktion kutsu voidaan kirjoittaa esimerkiksi (display (a_fun (aa bb cc) (bb aa)) ja Haskell-funktion kutsu main = do print(b_fun ("aa", ["bb","aa","cc"])) Vastaus. a) Scheme-funktio (define (a_fun x y) (cond ((null? x) y) (else (cons (car x) (a_fun (cdr x) y))))) ottaa siis kaksi parametria, joista ainakin ensimmäisen tulee olla lista, koska siihen sovelletaan listojen operaatioita. Schemessä cond määrittelee monivalintalauseen toteuttavan funktion. Kun kohdataan ensimmäinen lauseke, joka evaluoituu todeksi, palautetaan sitä vastaava arvo: Jos siis x on tyhjä lista, palautetaan y. Jos x ei ole tyhjä, sovelletaan ensin funktiota x:n häntään (cdr x) ja y:hyn. Sitten x:n päästä (car x) ja muodostuneesta listasta muodostetaan lista. Jos x:ssä on vain yksi alkio (häntä on tyhjä), niin tämä alkio liitetään listan y alkuun. Rekursiivisesti päättelemällä huomataan, että funktio liittää listan x listan y alkuun. Tämän voi varmistaa kokeilemalla funktiota eri syötteillä tulkissa. Esimerkiksi (display (a_fun '(1 2 3) '(a b))) tulostaa (1 2 3 a b). b) Haskell-funktio b_fun(x,[]) = [] b_fun(x,(y:ys)) = if x==y then (y:ys) else b_fun(x,ys) ottaa myös kaksi parametria, joista jälkimmäinen on lista. Haskellissa voidaan funktioita määritellä hahmontunnistuksen avulla. Edellä oleva funktio palauttaa tyhjän listan, jos jälkimmäinen parametri on tyhjä lista. Jos jälkimmäinen parametri ei ole tyhjä lista, se jaetaan päähän y ja häntään ys. Jos parametri x on sama kuin listan pää, palautetaan lista. Muuten kutsutaan funktiota
rekursiivisesti parametreilla x ja listan häntä. Näin edetään, kunnes alkio x löytyy listasta ja palautetaan x:llä alkava listan osa, tai lista tyhjenee, jolloin palautetaan tyhjä lista. Funktio siis etsii listasta y loppuosan joka alkaa x:llä. Tämän voi myös tarkistaa tulkilla, esimerkiksi syötteellä main = do print (b_fun(3,[1,2,3,4,5])) tulostuu [3,4,5]. Tehtävä 3. Kirjoita Scheme-funktio kaanna, joka kääntää argumenttinaan saamansa listan. Listan syvärakennetta ei tarvitse kääntää, ts. syötteellä (a b c d) funktio palauttaa listan (d c b a) ja syötteellä (a (b c) (d e) (f g)) listan ((f g) (d e) (b c) a). Voit käyttää hyväksi Schemen varusfunktiota append, joka yhdistää parametrina annettavat kaksi listaa. Kannattanee kirjoittaa funktio tekstitiedostoon, josta kopioi Scheme-tulkkiin. Testaa funktiotasi. Vastaus. Lista voidaan kääntää rekursiivisesti siten, että tyhjän listan kääntäminen palauttaa tyhjän listan. Muuten lista käännetään niin, että ensin käännetään listan häntä ja käännetyn hännän perään liitetään listan pää. Näin saadaan rekursiivinen funktio (define (kaanna x) (cond ((null? x) '()) ((not (list? x)) x) (else (append (kaanna (cdr x)) (list (car x)))))) Yllä on otettu vielä huomioon, että funktiolle voidaan syöttää myös parametri joka ei ole lista. Tällöin palautetaan parametri sellaisenaan. Huomaa vielä, että listan pää ei välttämättä ole lista, joten siitä muodostetaan lista ennen sen lisäämistä käännetyn hännän perään.
Tehtävä 4. Kirjoita Scheme-funktio rullaa, joka tuottaa argumenttinaan saamastaan listasta sen kaikki kierrot eli rotaatiot ja esittää ne yhdessä listassa. Syötteellä (a b c d) funktio palauttaa siis listan ((a b c d) (b c d a) (c d a b) (d a b c)). Vihje: Toteutus tulee helpommaksi, jos kirjoittaa apufunktioita. Vastaus. Kirjoitetaan ensin apufunktio turn, joka tekee yhden rotaation, ts. siirtää listan pään sen viimeiseksi alkioksi: (define (turn x) (append (cdr x) (list (car x)))) Tämä funktio palauttaa esimerkiksi parametrilistalla (1 2 3 4) listan (2 3 4 1). Tätä funktiota toistuvasti soveltamalla saadaan kaikki rotaatiot. Pitää vain pystyä kontrolloimaan sitä, että saadaan täsmälleen oikea määrä rotaatioita. Rotaatioita on yhtä monta kuin listassa on alkioita, joten kirjoitetaan apufunktio roll, joka ottaa parametrinaan kaksi listaa. Ensimmäistä kierretään ja toista lyhennetään jokaisella kutsulla. Parametrilista lisätään palautettavan listan alkioksi. Rekursio päättyy, kun toinen lista tyhjenee: (define (roll x y) (cond ((null? x) '() ) ((null? y) '() ) (else (append (list x) (roll (turn x) (cdr y)) )))) Näin saadaan listan x kaikki rotaatiot yhteen listaan, kun kirjoitetaan funktio, joka kutsuu edellistä funktiota sama lista kumpanakin parametrina: (define (rullaa x) (if (list? x) (roll x x) '())) Tällöin kutsu (rullaa (1 2 3 4)) palauttaa listan ((1 2 3 4) (2 3 4 1) (3 4 1 2) (4 1 2 3))
Tehtävä 5. Kirjoita Haskellilla tehtävien 3 ja 4 funktiot. Vastaus. Käytetään samaa logiikkaa kuin Scheme-funktioissa. Tehtävän 3 funktio voidaan kirjoittaa seuraavasti: kaanna [] = [] kaanna (x:xs) = kaanna(xs) ++ [x] Huomaa, että Haskellissa listat kirjoitetaan hakasulkeisiin ja alkiot erotetaan pilkuilla. Haskellissa listan pää ja häntä voidaan käsitellä hahmontunnistuksella (x:xs) kuten yllä. Listoja yhdistellään operaattorilla ++. Tehtävän 4 funktio laaditaan samalla periaatteella kuin Scheme-funktio kahden apufunktion avulla. turn [] = [] turn (x:xs) = xs ++ [x] roll (x,[]) = [] roll (x,(y:ys)) = [x] ++ roll(turn(x),ys) rullaa x = roll(x,x) Haskell-koodi lienee kieliä tuntemattomalle luettavampaa kuin Scheme.