Ohjelmoinnin peruskurssien laaja oppimäärä

Samankaltaiset tiedostot
Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Scheme-kesäkurssi luento 2

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

TIEA341 Funktio-ohjelmointi 1, kevät 2008

815338A Ohjelmointikielten periaatteet

Ohjelmoinnin peruskurssien laaja oppimäärä

Scheme-kesäkurssi luento 3

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Scheme-kesäkurssi luento 5

Luku 3. Listankäsittelyä. 3.1 Listat

Ohjelmoinnin peruskurssien laaja oppimäärä

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Laiska laskenta, korekursio ja äärettömyys. TIEA341 Funktio ohjelmointi Syksy 2005

Scheme-kesäkurssi luento 1

Scheme-kesäkurssi luento 4

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Tämän vuoksi kannattaa ottaa käytännöksi aina kirjoittaa uuden funktion tyyppi näkyviin, ennen kuin alkaa sen määritemää kirjoittamaan.

TIEA341 Funktio-ohjelmointi 1, kevät 2008

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Jatkeet. TIES341 Funktio ohjelmointi 2 Kevät 2006

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA341 Funktio-ohjelmointi 1, kevät 2008

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Ohjelmoinnin peruskurssien laaja oppimäärä

Abstraktit tietotyypit. TIEA341 Funktio ohjelmointi 1 Syksy 2005

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Funktionaalinen ohjelmointi

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Tietorakenteet ja algoritmit - syksy

Haskell ohjelmointikielen tyyppijärjestelmä

Monadeja siellä, monadeja täällä... monadeja kaikkialla? TIES341 Funktio ohjelmointi 2 Kevät 2006

Geneeriset tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

ITKP102 Ohjelmointi 1 (6 op)

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TAMPEREEN TEKNILLINEN YLIOPISTO

Algoritmit 1. Luento 3 Ti Timo Männikkö

ITKP102 Ohjelmointi 1 (6 op)

Makrojen mystinen maailma lyhyt oppimäärä

Funktionaalinen ohjelmointi

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Tietorakenteet ja algoritmit

Ohjelmoinnin perusteet Y Python

Kokeellista matematiikkaa SAGE:lla

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

Bootstrap / HTDP2 / Realm of Racket. Vertailu

Ohjelmoinnin peruskurssi Y1

Tutoriaaliläsnäoloista

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Ohjelmoinnin perusteet Y Python

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

Luento 5. Timo Savola. 28. huhtikuuta 2006

Ohjelmoinnin perusteet Y Python

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Ohjelmoinnin perusteet Y Python

TAMPEREEN TEKNILLINEN YLIOPISTO

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssi Y1

(p j b (i, j) + p i b (j, i)) (p j b (i, j) + p i (1 b (i, j)) p i. tähän. Palaamme sanakirjaongelmaan vielä tasoitetun analyysin yhteydessä.

Tyyppiluokat II konstruktoriluokat, funktionaaliset riippuvuudet. TIES341 Funktio-ohjelmointi 2 Kevät 2006

5.5 Jäsenninkombinaattoreista

Mitä funktionaalinen ohjelmointi on

Ohjelmoinnin perusteet Y Python

Funktionaalisten kielten oppimisesta ja valinnasta

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin perusteet Y Python

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

Olio-ohjelmointi Syntaksikokoelma

Racket ohjelmointia II. Tiina Partanen 2015

Transkriptio:

Ohjelmoinnin peruskurssien laaja oppimäärä Luento 6: Funktionaalista ohjelmointia: todistamisesta, virrat ja I/O, hahmonsovitus (mm. SICP 3.5) Riku Saikkonen 8. 11. 2011

Sisältö 1 Vähän funktionaalisten ohjelmien todistamisesta 2 Funktionaalista ohjelmointia: virrat 3 Funktionaalinen I/O 4 Funktionaalista ohjelmointia: hahmonsovitussyntaksi

Ohjelmien oikeaksi todistamisesta eräs funktionaalisuuden etu on, että ohjelmista on helpompi todistaa ominaisuuksia, jos ne ovat puhtaasti funktionaalisia lausekkeita voi muuttaa toisiksi matemaattisesti eli välittämättä ympäröivästä kontekstista tai ohjelman tilasta suoritushetkellä imperatiivisen koodin todistamisessa mietitään yleensä ohjelman tilaa tietyillä hetkillä todistus on usein ketju ohjelman välitiloja alkutilasta (argumentit) lopputilaan, joka toteuttaa todistettavan ehdon suoritusjärjestyksellä on siis koko ajan väliä silmukoille pitää keksiä invariantti: ehto joka on voimassa esim. jokaisen kierroksen alussa ja lopussa toinen funktionaalisuuden etu on, että koodi ja todistus voi olla abstraktimpaa: voidaan tehdä enemmän yleiskäyttöisiä apufunktioita ja todistaa ensin niiden ominaisuuksia

Funktionaalinen todistusesimerkki 1/2 Todistetaan: kaikille listoille l: (accumulate cons nil l) l (lista = nil:iin päättyvä jono pareja) Ensin accumulate:n määritelmä (define (accumulate op initial sequence) (if (null? sequence) initial (op (car sequence) (accumulate op initial (cdr sequence))))) Tai sanallinen määritelmä (jolloin toteutuksen ei tarvitse olla täsmälleen ylläoleva): 1 (accumulate op i '()) i 2 (accumulate op i (cons h t ) ( op h (accumulate op i t ))

Funktionaalinen todistusesimerkki 2/2 Todistetaan: kaikille listoille l: (accumulate cons nil l) l (lista = nil:iin päättyvä jono pareja) Todistus 1 Jos l = nil: (accumulate cons nil nil) nil (accumulate:n määritelmästä) 2 Jos l = pari (h,t) eli ((car l), (cdr l)): (accumulate cons nil (cons h t)) (cons h (accumulate cons nil t)) (taas määritelmästä, op = cons) Koska t on lyhyempi kuin (cons h t), tämä riittää induktioon.

Listankäsittelyproseduurien ominaisuuksia 1/2 tässä muutamia vastaavalla tavalla todistettavia ominaisuuksia (kaikki listat ovat nil:iin päättyviä ja argumenttifunktiot päättyviä ja sivuvaikutuksettomia) tarkoittaa, että lausekkeiden arvot ovat samoja suoritusaika tai laskujärjestys voi olla eri reverse:stä: (reverse (reverse l)) l lähinnä map:iin liittyviä: (map f (reverse l)) (reverse (map f l)) (map f (append l1 l2)) (append (map f l1) (map f l2)) (map (compose f g) l) (map (lambda (x) (f (g x))) l) (map f (map g l)) (filter p (map f l)) (map f (filter (lambda (x) (p (f x))) l))

Listankäsittelyproseduurien ominaisuuksia 2/2 fold:eihin eli accumulate:en liittyviä: (fold-right op i l) (fold-left op i l), jos op on assosiatiivinen ja kaikille x: (op x i) x ja (op i x) x (fold-right op i l) (fold-left (flip op) i (reverse l)), missä (define (flip f) (lambda (x y) (f y x))) alla olevat kaksi reverse:n toteutusta toimivat eli (rev-1 l) (rev-2 l) (reverse l) Kaksi toteutusta reverse:stä (define (rev-1 l) (if (null? l) l (append (rev-1 (cdr l)) (list (car l))))) (define (rev-2 l) (fold-left (flip cons) nil l))

Sisältö 1 Vähän funktionaalisten ohjelmien todistamisesta 2 Funktionaalista ohjelmointia: virrat 3 Funktionaalinen I/O 4 Funktionaalista ohjelmointia: hahmonsovitussyntaksi

Virrat eli mahdollisesti äärettömät listat (SICP 3.5) virta (stream) on lista, josta mahdollisesti vain osa on valmiina samoin kuin lista, virta muodostuu tyhjästä virrasta the-empty-stream (sama kuin nil) pareista, joita tehdään cons-stream:lla parin osat ovat stream-car ja stream-cdr virta on jono pareja joka päättyy tyhjään virtaan virtojen erikoisuus on se, että virtaparin cdr:ää ei evaluoida paria tehdessä, vaan vasta kun cdr suoritetaan joten voi määritellä myös äärettömän virran, jonka cdr:istä löytyy lisää alkioita niin paljon kun niitä jaksaa hakea virrat ovat käytössä muuallakin funktionaalisessa ohjelmoinnissa: Haskell-kielessä kaikki listat toimivat näin virtojen vastine löytyy mm. Scalasta joissain toteutuksissa myös car lasketaan vasta tarvittaessa

Miksi virtoja? (SICP 3.5.1) listankäsittelyoperaatioissa on se ongelma, että (ainakin periaatteessa) niissä tehdään usein pitkiä välituloslistoja ennen lopullisen tuloksen laskemista virrat auttavat tässä: välituloslistoista lasketaan vain niin paljon alkioita kuin jatkoa varten on tarpeen toinen virtojen etu on, että äärettömiä virtoja määrittelemällä ja käyttämällä voi saada aikaan siistimpää koodia kolmas virtojen käyttötarkoitus on muuttuvan tilan mallinnuksessa tallennetaan virtaan peräkkäisiä tiloja sen sijaan että esim. olio muuttaisi omia tilamuuttujiaan ei-funktionaalisesti virroilla voi tehdä myös I/O:ta funktionaalisesti huono puoli: virtoja käytettäessä on vaikea ennustaa, milloin tietty koodi suoritetaan tästä syystä virtoja käytetään yleensä vain puhtaasti funktionaalisessa koodissa (ei sijoituslauseiden kanssa)

Äärettömät virrat (SICP 3.5.2) rekursion avulla voi määritellä äärettömiä virtoja virta voi myös viitata itseensä cdr:ssä useimmat listojenkäsittelyoperaatiot toimivat samaan tyyliin myös äärettömillä virroilla Esimerkkejä esim. (stream-map f s) palauttaa uuden äärettömän virran kaikki eivät toimi: esim. äärettömän virran reverse jäisi etsimään viimeistä alkiota (define ones (cons-stream 1 ones)) ; (1 1 1 1 1 1...) (define (integers-starting-from n) (cons-stream n (integers-starting-from (+ n 1)))) (define integers (integers-starting-from 1)) ; (1 2 3...) (define (fibgen a b) (cons-stream a (fibgen b (+ a b)))) (define fibs (fibgen 0 1)) ; (0 1 1 2 3 5 8 13 21...)

Esimerkki: kaikki alkuluvut sisältävä virta (SICP 3.5.2) Esimerkkikoodi (define (divisible? x y) (= (remainder x y) 0)) (define (integers-starting-from n) (cons-stream n (integers-starting-from (+ n 1)))) (define (sieve stream) (cons-stream (stream-car stream) (sieve (stream-filter (lambda (x) (not (divisible? x (stream-car stream)))) (stream-cdr stream))))) (define primes (sieve (integers-starting-from 2))) primestream.scm stream-filter on samanlainen kuin filter, mutta virroille (kirjassa on samoin stream-map jne.)

Esimerkki: neliöjuuren laskenta (SICP 3.5.3) Esimerkkikoodi sqrtstream.scm (define (sqrt-improve guess x) (average guess (/ x guess))) (define (sqrt-stream x) (define guesses (cons-stream 1.0 (stream-map (lambda (guess) (sqrt-improve guess x)) guesses))) guesses) Testiajo: (sqrt-stream 2) (1. 1.5 1.416667 1.414216...) kirjan alussa (luku 1.1) laskettiin samaa häntärekursiolla tässä ratkaisussa ei tarvita lopetusehtoa (haluttua tarkkuutta) kaikki lasketut arvot (eli laskennan välitilat) ovat virrassa tallessa ja virran seuraavan arvon laskemisessa voisi käyttää useampia aiempia arvoja häntärekursiivisessa (tai silmukalla tehdyssä) ratkaisussa käytettävissä on vain edellinen tila

Sisältö 1 Vähän funktionaalisten ohjelmien todistamisesta 2 Funktionaalista ohjelmointia: virrat 3 Funktionaalinen I/O 4 Funktionaalista ohjelmointia: hahmonsovitussyntaksi

I/O:n ongelma (osin SICP 3.5.5) puhtaasti funktionaalisessa ohjelmoinnissa millään operaatiolla ei saisi olla sivuvaikutuksia laskennan tilan ylläpitoa voi tehdä mm. rekursiolla ja virroilla entä interaktio käyttäjän (tai esim. verkon) kanssa? toinen vastaava ongelma on tilallinen funktio, jonka sisäistä tilaa ei haluaisi käsitellä koko ajan: esim. satunnaislukugeneraattori funktionaaliseen I/O:hon ei ole täydellistä ratkaisua (vielä?) perusratkaisu on, että pääohjelma on funktio syötevirralta tulosvirralle (esimerkki kohta) Haskell-kielessä tämän idean päälle on rakennettu koodia siistivä mutta käsitteellisesti monimutkainen abstraktio nimeltä monadi toinen ratkaisu (esim. Clean-kielessä) on tyyppijärjestelmän laajennus niin, että tiettyjä arvoja voi käyttää vain kerran (uniqueness type) tätä ongelmaa tutkitaan vielä... käytännön ratkaisu on usein eristää I/O:ta tekevä koodi muusta enemmän funktionaalisesta koodista

I/O virroilla: toteutus Virta-I/O:n toteuttaminen Schemessä (define (run-io io-proc) (define (get-input-stream) (cons-stream (delay (read)) (get-input-stream))) (define (display-output-stream stream) (if (stream-null? stream) 'end (begin (display (stream-car stream)) (newline) (display-output-stream (stream-cdr stream))))) (display-output-stream (io-proc (get-input-stream)))) interaktiivinen pääohjelma on (run-io io-proc), jossa io-proc on oma proseduuri, joka ottaa argumentiksi virran käyttäjältä tulevia syötteitä (read:lla luettuja Scheme-arvoja) palauttaa virran tulosteita (tulostettavaksi display:llä) run-io tulostaa tulosvirtaa aina kun sitä on saatavilla, ja tuottaa lisää syötevirtaa io-proc:n sitä pyytäessä

I/O virroilla: käyttöesimerkki Edellisen toteutuksen käyttäminen (define (io-sqrt input-stream) ; palauttaa tulostevirran (cons-stream "Enter a number or q to quit:" (let ((input (force (stream-car input-stream)))) (if (eq? input 'q) the-empty-stream (cons-stream "The square root is:" (cons-stream (sqrt input) (io-sqrt (stream-cdr input-stream)))))))) iosqrt.scm (run-io io-sqrt) ; ''pääohjelman'' käynnistys tulostaa siis tulosvirtaan sekä kehotteita että laskemisen tuloksia tässä esimerkissä virta, jonka car olisi myös viivästetty, toimisi paremmin kuin SICP-kirjan virta: siksi force ja edellä delay

I/O virroilla: tilallinen käyttöesimerkki (osin SICP 3.3.5) Pankkitili, jolta voi nostaa ja tallettaa rahaa iobank.scm (define (io-bank balance input-stream) ; tila on balance:ssa (cons-stream (string-append "Current balance: " (number->string balance) " euros") (let ((input (force (stream-car input-stream)))) (if (eq? input 'q) the-empty-stream (let ((new-balance (+ balance input))) (if (< new-balance 0) (cons-stream "Error: not enough money!" (io-bank balance (stream-cdr input-stream))) (cons-stream (string-append (if (< input 0) "Ok, withdrawed " "Ok, deposited ") (number->string input) " euros.") (io-bank new-balance (stream-cdr input-stream))))))))) (define (io-bank-run input-stream) (io-bank 0 input-stream)) (run-io io-bank-run) ; apuproseduuri käynnistykseen

Sisältö 1 Vähän funktionaalisten ohjelmien todistamisesta 2 Funktionaalista ohjelmointia: virrat 3 Funktionaalinen I/O 4 Funktionaalista ohjelmointia: hahmonsovitussyntaksi

Hahmonsovitus (pattern matching) funktionaalisissa kielissä suosittu erikoissyntaksi (ei Schemessä) funktion määrittelyssä voi olla argumentin sijasta hahmo, johon argumentin tulee sopia ja joka voi asettaa muuttujille arvoja samalle funktiolle voi olla useampia hahmoja, jotka käydään läpi järjestyksessä esimerkiksi (nämä esimerkit ovat Haskell-kieltä): fact 0 = 1 fact n = n * fact (n-1) hahmonsovitus muuttuu periaatteessa if-lauseiksi, jotka kokeilevat hahmoja (argumentit vasemmalta oikealle, hahmot ylhäältä alas): fact n = if n == 0 then 1 else n * fact (n-1) hahmonsovitus on yleensä mahdollista myös funktion sisällä (esim. match) sekä let- ja lambda-lauseissa vastaava idea säännöllisille lausekkeille löytyy esim. Perl-kielestä tässä hahmoon sovitetaan merkkijonoja eikä tietotyyppejä Perl-esimerkki: ($hour, $min, $sec) = ($time = /([0-9][0-9]):([0-9][0-9]):([0-9][0-9])/);

Hahmonsovitus: rakenteet hahmossa voi olla myös listarakenne: sumlist [] = 0 sumlist (x:xs) = x + sumlist xs tässä [] = tyhjä lista ja (x:xs) = pari, jonka car ja cdr talletetaan muuttujiin x ja xs tai itsemääritellyistä tyypeistä koostuva rakenne: data Tree a = Leaf a Branch (Tree a) (Tree a) fringe (Leaf x) = [x] fringe (Branch left right) = fringe left ++ fringe right erikoisuuksia: _ sopii mihin tahansa arvoon, ei tee muuttujaa; syntaksissa xl@(x:xs) koko lista on xl car (x:_) = x cdr (_:x) = x f xl@(x:xs) = xl ++ xs ++ [x] Testiajo: f [1,2,3] [1,2,3,2,3,1]

Vahdit (guard) hahmon lisäksi sovitukseen voi määritellä ehtoja, joiden on oltava tosia, jotta arvo sopisi hahmoon: sign x x > 0 = 1 x == 0 = 0 x < 0 = -1 viimeinen haara voisi olla myös otherwise = -1 vahdit tarkistetaan varsinaisen sovituksen jälkeen, joten sovituksen määrittelemiä muuttujia voi käyttää (kuten x:ää yllä) Monimutkaisempi Haskell-esimerkki mergenodups [] ys = ys mergenodups xs [] = xs mergenodups xl@(x:xs) yl@(y:ys) x < y = x : mergenodups xs yl x == y = mergenodups xs yl otherwise = y : mergenodups xl ys

Hahmonsovitus käytännössä hahmonsovitus on Haskellin lisäksi käytössä esim. ML:ssä, Ocamlissa, Erlangissa, Prologissa ja (osin) Scalassa säännöllisiin lausekkeisiin perustuva hahmonsovitus monessa muussakin kielessä hahmonsovituksen etu: koodi on lyhyempää ja lähempänä matemaattista määritelmää (siis helppolukuisempaa, ainakin matemaatikolle... ) hahmonsovituksen ongelmia: ei toimi hyvin abstraktien tietotyyppien kanssa: hahmoa kirjoittaessa pitää tietää mitä kaikkia osia tietotyypissä on ja missä järjestyksessä syntaksi toimii siististi vain lyhyillä hahmoilla ja vahdeilla