Abstraktit tietotyypit TIEA341 Funktio ohjelmointi 1 Syksy 2005
Data abstraktio Abstraktio on ohjelmoinnin tärkein väline Data abstraktio abstrahoi dataa
Abstrakti tietotyyppi Koostuu kolmesta asiasta: joukosta arvoja, joiden sisäinen rakenne on piilotettu joukosta operaatioita, joilla näitä arvoja voidaan manipuloida aksioomista, jotka määrittelevät, miten ko. operaatiot toimivat keskenään yhteen Esimerkkejä Float, Double Array joukkotyyppi assosiaatiotaulu
Klassinen esimerkki: pino Pino on parametrisoitu tyyppi, Stack a a tyyppisten arvojen pino Perusoperaatiot: empty :: Stack a push :: a > Stack a > Stack a pop :: Stack a > Stack a peek :: Stack a > a isempty :: Stack a > Bool
Pinon aksioomat peek (push a s) == a pop (push a s) == s isempty (push a s) == False isempty empty == True
Eräs toteutus data Stack a = Empty Push a (Stack a) empty = Empty push = Push pop (Push _ s) = s pop Empty = undefined peek (Push e _) = e peek Empty = undefined isempty Empty = True isempty (Push ) = False
Toinen toteutus newtype määrittelee uudent tyypin kuten data (tarkemmin seuraavalla kalvolla) newtype Stack a = Stack [a] empty = Stack [] push a s = Stack (a : s) pop (Stack s) = Stack (tail s) peek (Stack s) = head s isempty (Stack s) = null s
Sivuunmennen sanoen: newtype päällisin puolin samanlainen kuin data rajoituksia: vain yksi datakoostin! datakoostimella täsmälleen yksi parametri! miksi? tehokkuussyyt: ajon aikana Foo A täsmälleen samanlainen kuin A (jos Foo määritelty newtypellä) silti oma tyyppinsä, ei alias kuten type avainsanalla luodut
Pinon aksioomat, taas kerran Miten varmistumme, että toteutus täyttää määrittelyn? Todistetaan, että toteutus pitää aksioomat voimassa Kullekin aksioomalle on siis todistettava, että se on voimassa riippumatta siinä esiintyvien muutujien arvoista, kun operaatiot ja tyyppi on määritelty niin kuin ne on
Yksi todistus pop (push a s) == pop (Push a s) pushin määritelmä == case Push a s popin määritelmä of Empty > undefined Push _ s' > s' == s casen sievennys
Yksi todistus Leibnitzin laki Samat voidaan aina korvata toisillaan. Tämä on monen mielestä tärkein ero funktioohjelmoinnin ja muunlaisen ohjelmoinnin välillä: funktio ohjelmoinnissa Leibnitzin laki pätee johdonmukaisesti aina ja kaikkialla. pop (push a s) == pop (Push a s) pushin määritelmä == case Push a s popin määritelmä of Empty > undefined Push _ s' > s' == s casen sievennys
Arvojen sisäisen rakenteen piilottaminen Kumpikin toteutus jättää pinojen sisäisen rakenteen näkyville Tarvitaan tapa, jolla se voidaan piilottaa Haskellissa siihen käytetään moduleita
Tietotyypin nimi viedään, mutta sen datakoostimia ei Niin sanottu vientilista: luettelo nimistä, jotka näkyvät modulin käyttäjille module Stack (Stack (), empty, push, peek, pop, isempty) where newtype Stack a = Stack [a]...
module Foo where import Stack (Stack) import qualified Stack foo :: Stack a foo = Stack.empty tuodaan Stack modulista vain Stack tyyppi sellaisenaan tuodaan kaikki Stack modulin vientilistaan kirjatut nimet niin, että niiden eteen pitää kirjoittaa Stack
Taulukko Tyyppi: Array inx dat Operaatiot: new :: Integral inx => inx > inx > dat > Array inx dat update :: Integral inx => Array inx dat > inx > dat > Array inx dat lookup :: Integral inx => Array inx dat > inx > dat fold :: Integral inx => (inx > dat > a > a) > a > Array inx dat > a bounds :: Integral inx => Array inx dat > (inx, inx)
Taulukon dilemma Perinteinen taulukko on erittäin huono funktioohjelmoinnissa pienikin päivitys aiheuttaa koko taulukon kopioinnin Taulukkoa käytetään usein niin, että peräkkäisiä päivityksiä on paljon, kuten myös peräkkäisiä hakuja mutta ei aina näin! Toteutusidea: binäärinen trie (huom. ei tree!)
Binäärinen trie 1 1 11 solmuja n log n kpl haku ja päivitysaika 0 1 10 01 log n päivitys rakentaa log n solmua uudestaan edellä n on indeksirajojen 0 0 00 erotus
data Trie a = Inner (Trie a) (Trie a) Leaf a data Array inx dat = Array (inx,inx) (Trie dat) new lb ub d = Array (lb,ub) (t logn) where logn = ceiling (log (ub lb) / log 2) t n n > 0 = Inner t' t' otherwise = Leaf d where t' = t (n 1) lookup (Array (lb,ub) t) i lb <= i && i < ub = lu (i lb) t otherwise = undefined where lu 0 (Inner z o) = lu 0 z lu 0 (Leaf d) = d lu n (Inner z o) = case bit of 0 > lu n' z 1 > lu n' o where (n',bit) = divmod n 2
Jono module Queue (Queue (), empty. isempty, enqueue, dequeue, peek) where empty :: Queue a isempty :: Queue a > Bool enqueue :: a > Queue a > Queue a dequeue :: Queue a > Queue a peek :: Queue a > a
Jonon toteutus newtype Queue a = Q [a] empty = Q [] isempty (Q q) = null q enqueue e (Q q) = Q (e : q) dequeue (Q q) = Q (init q) peek (Q q) = last q Tehoton! dequeue ja peek ovat O(n)!
Jonon toteutus jekku tekee hyvää newtype Queue a = Q [a] [a] empty = Q [] [] isempty (Q qb qf) = null qb && null qf enqueue e (Q qb qf) = Q (e : qb) qf dequeue (Q qb (_:qf)) = Q qb qf dequeue (Q qb@(_:_) []) = dequeue (Q [] (reverse qb)) peek (Q _ (e:_)) = e F. W. Burton: An efficient implementation of FIFO queues, Information Processing Letters, vol. 14, pp. 205 206, 1982.
Joukot module Set (Set, empty, isempty. memb, unit, union, intersection, difference, subset) where empty :: Ord a => Set a isempty :: Ord a => Set a > Bool memb :: Ord a => a > Set a > Bool unit :: Ord a => a > Set a > Set a union, intersection, difference :: Ord a => Set a > Set a > Set a subset :: Ord a => Set a > Set a > Bool
Joukot toteutus import BinTree newtype Set a = Set (BinTree a ()) empty = EmptyTree isempty (Set (BTEmpty)) = True isempty (Set (BTNode )) = False memb e (Set s) = case BinTree.lookup e s of Just () > True Nothing > False...
Äärelliset kuvaukset module Map (Map, empty, isempty, haskey insert, lookup) where empty :: Ord a => Map a b isempty :: Ord a => Map a b > Bool haskey :: Ord a => a > Map a b > Bool insert :: Ord a => a > b > Map a b > Map a b lookup :: Ord a => a > Map a b > Maybe b
Vakiokirjastossa... module Array (eli module Data.Array) module Data.Set module Data.Map module Data.Queue